3d-tiles-renderer
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
277 lines (171 loc) • 5.89 kB
JSX
import { createContext, useContext, useEffect, useRef, forwardRef, useMemo, useCallback, useState } from 'react';
import { useThree, useFrame } from '@react-three/fiber';
import { Object3D } from 'three';
import { TilesRenderer as TilesRendererImpl } from '../../three/TilesRenderer.js';
import { useDeepOptions } from '../utilities/useOptions.js';
import { useObjectDep } from '../utilities/useObjectDep.js';
import { useApplyRefs } from '../utilities/useApplyRefs.js';
import { WGS84_ELLIPSOID } from '../../three/math/GeoConstants.js';
// context for accessing the tile set
export const TilesRendererContext = createContext( null );
export const TilesPluginContext = createContext( null );
// group that matches the transform of the tile set root group
function TileSetRoot( { children } ) {
const tiles = useContext( TilesRendererContext );
const ref = useRef();
useEffect( () => {
if ( tiles ) {
ref.current.matrixWorld = tiles.group.matrixWorld;
}
}, [ tiles ] );
return <group ref={ ref } matrixWorldAutoUpdate={ false } matrixAutoUpdate={ false }>{ children }</group>;
}
export function EastNorthUpFrame( props ) {
const {
lat = 0,
lon = 0,
height = 0,
az = 0,
el = 0,
roll = 0,
ellipsoid = WGS84_ELLIPSOID.clone(),
children,
} = props;
const tiles = useContext( TilesRendererContext );
const invalidate = useThree( state => state.invalidate );
const [ group, setGroup ] = useState( null );
const updateCallback = useCallback( () => {
if ( group === null ) {
return;
}
// hide the group if the tiles aren't loaded yet
const localEllipsoid = tiles && tiles.ellipsoid || ellipsoid || null;
group.matrix.identity();
group.visible = Boolean( tiles && tiles.root || ellipsoid );
if ( localEllipsoid === null ) {
return;
}
localEllipsoid.getFrame( lat, lon, az, el, roll, height, group.matrix );
group.matrix.decompose( group.position, group.quaternion, group.scale );
group.updateMatrixWorld();
invalidate();
}, [ invalidate, tiles, lat, lon, height, az, el, roll, ellipsoid, group, useObjectDep( ellipsoid.radius ) ] ); // eslint-disable-line react-hooks/exhaustive-deps
// adjust the matrix world update logic if a tile set is present so that we can position the frame
// correctly regardless of the parent.
useEffect( () => {
if ( tiles !== null && group !== null ) {
group.updateMatrixWorld = function ( force ) {
if ( this.matrixAutoUpdate ) {
this.updateMatrix();
}
if ( this.matrixWorldNeedsUpdate || force ) {
this.matrixWorld.multiplyMatrices( tiles.group.matrixWorld, this.matrix );
force = true;
}
const children = this.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
const child = children[ i ];
child.updateMatrixWorld( force );
}
};
return () => {
group.updateMatrixWorld = Object3D.prototype.updateMatrixWorld;
};
}
}, [ tiles, group ] );
useEffect( () => {
updateCallback();
}, [ updateCallback ] );
// update the position when a tile set is loaded since it may modify the ellipsoid
useEffect( () => {
if ( tiles === null ) {
return;
}
tiles.addEventListener( 'load-tile-set', updateCallback );
return () => {
tiles.removeEventListener( 'load-tile-set', updateCallback );
};
}, [ tiles, updateCallback ] );
return <group ref={ setGroup }>{ children }</group>;
}
// component for registering a plugin
export const TilesPlugin = forwardRef( function TilesPlugin( props, ref ) {
const { plugin, args, children, ...options } = props;
const tiles = useContext( TilesRendererContext );
// create the instance
const instance = useMemo( () => {
if ( tiles === null ) {
return null;
}
let instance;
if ( Array.isArray( args ) ) {
instance = new plugin( ...args );
} else {
instance = new plugin( args );
}
return instance;
// we must create a new plugin if the tile set has changed
}, [ tiles, plugin, useObjectDep( args ) ] ); // eslint-disable-line
// assigns any provided options to the plugin
useDeepOptions( instance, options );
// assign ref
useApplyRefs( instance, ref );
// register the instance
useEffect( () => {
if ( tiles === null ) {
return;
}
tiles.registerPlugin( instance );
return () => {
tiles.unregisterPlugin( instance );
};
}, [ instance, tiles ] );
return <TilesPluginContext.Provider value={ instance }>{ children }</TilesPluginContext.Provider>;
} );
// component for adding a TilesRenderer to the scene
export const TilesRenderer = forwardRef( function TilesRenderer( props, ref ) {
const { url, group = {}, enabled = true, children, ...options } = props;
const [ camera, gl, invalidate ] = useThree( state => [ state.camera, state.gl, state.invalidate ] );
const tiles = useMemo( () => {
return new TilesRendererImpl( url );
}, [ url ] );
// create the tile set
useEffect( () => {
tiles.addEventListener( 'needs-render', () => invalidate() );
tiles.addEventListener( 'needs-update', () => invalidate() );
return () => {
tiles.dispose();
};
}, [ tiles, invalidate ] );
// update the resolution for the camera
useFrame( () => {
if ( tiles === null || ! enabled ) {
return;
}
camera.updateMatrixWorld();
tiles.setResolutionFromRenderer( camera, gl );
tiles.update();
} );
// add the camera
useEffect( () => {
if ( tiles === null ) {
return;
}
tiles.setCamera( camera );
return () => {
tiles.deleteCamera( camera );
};
}, [ tiles, camera ] );
// assign ref
useApplyRefs( tiles, ref );
// assign options recursively
useDeepOptions( tiles, options );
return <>
<primitive object={ tiles.group } { ...group } />
<TilesRendererContext.Provider value={ tiles }>
<TileSetRoot>
{ children }
</TileSetRoot>
</TilesRendererContext.Provider>
</>;
} );