UNPKG

3d-tiles-renderer

Version:

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

284 lines (197 loc) 7.48 kB
import { Vector3, Matrix4, MathUtils } from 'three'; // iterates over all present tiles in the given tileset at the given level in the given range export function forEachTileInBounds( range, level, tiling, callback ) { // pull the bounds in a bit to avoid loading unnecessary tiles. 1e-8 was chosen since smaller values // are not larger enough and cause extra tiles to load in cases where 1-to-1 tile-to-image should occur let [ minLon, minLat, maxLon, maxLat ] = range; minLat += 1e-8; minLon += 1e-8; maxLat -= 1e-8; maxLon -= 1e-8; const clampedLevel = Math.max( Math.min( level, tiling.maxLevel ), tiling.minLevel ); const [ minX, minY, maxX, maxY ] = tiling.getTilesInRange( minLon, minLat, maxLon, maxLat, clampedLevel, true ); for ( let x = minX; x <= maxX; x ++ ) { for ( let y = minY; y <= maxY; y ++ ) { callback( x, y, clampedLevel ); } } } // functions for generating UVs for cartographic-projected UVs function getGeometryCartographicChannel( geometry, geomToEllipsoidMatrix, ellipsoid ) { const _vec = new Vector3(); const _cart = {}; const uv = []; const posAttr = geometry.getAttribute( 'position' ); geometry.computeBoundingBox(); geometry.boundingBox.getCenter( _vec ).applyMatrix4( geomToEllipsoidMatrix ); // find a rough mid lat / lon point ellipsoid.getPositionToCartographic( _vec, _cart ); // fall back to 0 because if the geometry is exactly centered at 0, 0, 0 then // the calculated lat / lon will be NaN. const centerLat = _cart.lat || 0; const centerLon = _cart.lon || 0; let minLat = Infinity; let minLon = Infinity; let minHeight = Infinity; let maxLat = - Infinity; let maxLon = - Infinity; let maxHeight = - Infinity; for ( let i = 0; i < posAttr.count; i ++ ) { // get the lat / lon values per vertex _vec.fromBufferAttribute( posAttr, i ).applyMatrix4( geomToEllipsoidMatrix ); ellipsoid.getPositionToCartographic( _vec, _cart ); // The latitude calculations are not so stable at the poles so force the lat value to // the mid point to ensure we don't load an unnecessarily large of tiles // NOTE: this can distort the texture a bit at the poles if ( Math.abs( Math.abs( _cart.lat ) - Math.PI / 2 ) < 1e-5 ) { _cart.lon = centerLon; } // ensure we're not wrapping on the same geometry if ( Math.abs( centerLon - _cart.lon ) > Math.PI ) { _cart.lon += Math.sign( centerLon - _cart.lon ) * Math.PI * 2; } if ( Math.abs( centerLat - _cart.lat ) > Math.PI ) { _cart.lat += Math.sign( centerLat - _cart.lat ) * Math.PI * 2; } uv.push( _cart.lon, _cart.lat, _cart.height ); minLat = Math.min( minLat, _cart.lat ); maxLat = Math.max( maxLat, _cart.lat ); minLon = Math.min( minLon, _cart.lon ); maxLon = Math.max( maxLon, _cart.lon ); minHeight = Math.min( minHeight, _cart.height ); maxHeight = Math.max( maxHeight, _cart.height ); } const range = [ minLon, minLat, maxLon, maxLat ]; const region = [ ...range, minHeight, maxHeight ]; return { uv, range, region, }; } export function getMeshesCartographicRange( meshes, ellipsoid, meshToEllipsoidMatrix = null, projection = null, normalizedRange = null ) { // find the lat / lon ranges let minLat = Infinity; let minLon = Infinity; let minHeight = Infinity; let maxLat = - Infinity; let maxLon = - Infinity; let maxHeight = - Infinity; const uvs = []; const _matrix = new Matrix4(); meshes.forEach( mesh => { // multiply in the ellipsoid matrix if necessary _matrix.copy( mesh.matrixWorld ); if ( meshToEllipsoidMatrix ) { _matrix.premultiply( meshToEllipsoidMatrix ); } const { uv, region } = getGeometryCartographicChannel( mesh.geometry, _matrix, ellipsoid ); uvs.push( uv ); // save the min and max values minLat = Math.min( minLat, region[ 1 ] ); maxLat = Math.max( maxLat, region[ 3 ] ); minLon = Math.min( minLon, region[ 0 ] ); maxLon = Math.max( maxLon, region[ 2 ] ); minHeight = Math.min( minHeight, region[ 4 ] ); maxHeight = Math.max( maxHeight, region[ 5 ] ); } ); if ( projection !== null ) { // Clamp the mesh vertex range to the projection's valid bounds (e.g. ~±85° for Mercator) to // avoid NaN UV values for vertices that fall outside the projection range. This also stretches // the texture at the projection boundary rather than leaving gaps. // takes generate a normalized range if not already provided if ( normalizedRange === null ) { normalizedRange = projection.clampToBounds( [ minLon, minLat, maxLon, maxLat ] ); normalizedRange = projection.toNormalizedRange( normalizedRange ); } const [ minU, minV, maxU, maxV ] = normalizedRange; uvs.forEach( uv => { for ( let i = 0, l = uv.length; i < l; i += 3 ) { const lon = uv[ i + 0 ]; const lat = uv[ i + 1 ]; const h = uv[ i + 2 ]; let [ u, v ] = projection.toNormalizedPoint( lon, lat ); u = MathUtils.clamp( u, 0, 1 ); v = MathUtils.clamp( v, 0, 1 ); uv[ i + 0 ] = MathUtils.mapLinear( u, minU, maxU, 0, 1 ); uv[ i + 1 ] = MathUtils.mapLinear( v, minV, maxV, 0, 1 ); uv[ i + 2 ] = MathUtils.mapLinear( h, minHeight, maxHeight, 0, 1 ); } } ); } return { uvs, range: normalizedRange, region: [ minLon, minLat, maxLon, maxLat, minHeight, maxHeight ], }; } // functions for generating UVs for planar-projected UVs function getGeometryPlanarChannel( geometry, meshToFrame ) { const _vec = new Vector3(); const uv = []; const posAttr = geometry.getAttribute( 'position' ); let minU = Infinity; let minV = Infinity; let minW = Infinity; let maxU = - Infinity; let maxV = - Infinity; let maxW = - Infinity; for ( let i = 0; i < posAttr.count; i ++ ) { _vec.fromBufferAttribute( posAttr, i ).applyMatrix4( meshToFrame ); uv.push( _vec.x, _vec.y, _vec.z ); minU = Math.min( minU, _vec.x ); maxU = Math.max( maxU, _vec.x ); minV = Math.min( minV, _vec.y ); maxV = Math.max( maxV, _vec.y ); minW = Math.min( minW, _vec.z ); maxW = Math.max( maxW, _vec.z ); } // TODO: output a more complete bounds definition relative to the frame const range = [ minU, minV, maxU, maxV ]; return { uv, range, heightRange: [ minW, maxW ], }; } export function getMeshesPlanarRange( meshes, worldToFrame ) { // find the U / V ranges let minU = Infinity; let minV = Infinity; let minW = Infinity; let maxU = - Infinity; let maxV = - Infinity; let maxW = - Infinity; const uvs = []; const _matrix = new Matrix4(); meshes.forEach( mesh => { // multiply in the ellipsoid matrix if necessary _matrix.copy( mesh.matrixWorld ); if ( worldToFrame ) { _matrix.premultiply( worldToFrame ); } const { uv, range, heightRange } = getGeometryPlanarChannel( mesh.geometry, _matrix ); uvs.push( uv ); // save the min and max values minU = Math.min( minU, range[ 0 ] ); maxU = Math.max( maxU, range[ 2 ] ); minV = Math.min( minV, range[ 1 ] ); maxV = Math.max( maxV, range[ 3 ] ); minW = Math.min( minW, heightRange[ 0 ] ); maxW = Math.max( maxW, heightRange[ 1 ] ); } ); uvs.forEach( uv => { for ( let i = 0, l = uv.length; i < l; i += 3 ) { const u = uv[ i + 0 ]; const v = uv[ i + 1 ]; uv[ i + 0 ] = MathUtils.mapLinear( u, minU, maxU, 0, 1 ); uv[ i + 1 ] = MathUtils.mapLinear( v, minV, maxV, 0, 1 ); } } ); // TODO: output a more complete bounds definition relative to the frame return { uvs, range: [ minU, minV, maxU, maxV ], heightRange: [ minW, maxW ], }; }