3d-tiles-renderer
Version:
https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification
272 lines (181 loc) • 8.06 kB
JavaScript
import { I3DMLoaderBase } from '3d-tiles-renderer/core';
import { DefaultLoadingManager, Matrix4, InstancedMesh, Vector3, Quaternion } from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { WGS84_ELLIPSOID } from '../math/GeoConstants.js';
const tempFwd = /* @__PURE__ */ new Vector3();
const tempUp = /* @__PURE__ */ new Vector3();
const tempRight = /* @__PURE__ */ new Vector3();
const tempPos = /* @__PURE__ */ new Vector3();
const tempQuat = /* @__PURE__ */ new Quaternion();
const tempSca = /* @__PURE__ */ new Vector3();
const tempMat = /* @__PURE__ */ new Matrix4();
const tempMat2 = /* @__PURE__ */ new Matrix4();
const tempGlobePos = /* @__PURE__ */ new Vector3();
const tempEnuFrame = /* @__PURE__ */ new Matrix4();
const tempLocalQuat = /* @__PURE__ */ new Quaternion();
const tempLatLon = {};
export class I3DMLoader extends I3DMLoaderBase {
constructor( manager = DefaultLoadingManager ) {
super();
this.manager = manager;
this.adjustmentTransform = new Matrix4();
this.ellipsoid = WGS84_ELLIPSOID.clone();
}
resolveExternalURL( url ) {
return this.manager.resolveURL( super.resolveExternalURL( url ) );
}
parse( buffer ) {
return super
.parse( buffer )
.then( i3dm => {
const { featureTable, batchTable } = i3dm;
const gltfBuffer = i3dm.glbBytes.slice().buffer;
return new Promise( ( resolve, reject ) => {
const fetchOptions = this.fetchOptions;
const manager = this.manager;
const loader = manager.getHandler( 'path.gltf' ) || new GLTFLoader( manager );
if ( fetchOptions.credentials === 'include' && fetchOptions.mode === 'cors' ) {
loader.setCrossOrigin( 'use-credentials' );
}
if ( 'credentials' in fetchOptions ) {
loader.setWithCredentials( fetchOptions.credentials === 'include' );
}
if ( fetchOptions.headers ) {
loader.setRequestHeader( fetchOptions.headers );
}
// GLTFLoader assumes the working path ends in a slash
let workingPath = i3dm.gltfWorkingPath ?? this.workingPath;
if ( ! /[\\/]$/.test( workingPath ) ) {
workingPath += '/';
}
const adjustmentTransform = this.adjustmentTransform;
loader.parse( gltfBuffer, workingPath, model => {
const INSTANCES_LENGTH = featureTable.getData( 'INSTANCES_LENGTH' );
let POSITION = featureTable.getData( 'POSITION', INSTANCES_LENGTH, 'FLOAT', 'VEC3' );
const POSITION_QUANTIZED = featureTable.getData( 'POSITION_QUANTIZED', INSTANCES_LENGTH, 'UNSIGNED_SHORT', 'VEC3' );
const QUANTIZED_VOLUME_OFFSET = featureTable.getData( 'QUANTIZED_VOLUME_OFFSET', 1, 'FLOAT', 'VEC3' );
const QUANTIZED_VOLUME_SCALE = featureTable.getData( 'QUANTIZED_VOLUME_SCALE', 1, 'FLOAT', 'VEC3' );
const NORMAL_UP = featureTable.getData( 'NORMAL_UP', INSTANCES_LENGTH, 'FLOAT', 'VEC3' );
const NORMAL_RIGHT = featureTable.getData( 'NORMAL_RIGHT', INSTANCES_LENGTH, 'FLOAT', 'VEC3' );
const SCALE_NON_UNIFORM = featureTable.getData( 'SCALE_NON_UNIFORM', INSTANCES_LENGTH, 'FLOAT', 'VEC3' );
const SCALE = featureTable.getData( 'SCALE', INSTANCES_LENGTH, 'FLOAT', 'SCALAR' );
const RTC_CENTER = featureTable.getData( 'RTC_CENTER', 1, 'FLOAT', 'VEC3' );
const EAST_NORTH_UP = featureTable.getData( 'EAST_NORTH_UP' );
[
'NORMAL_UP_OCT32P',
'NORMAL_RIGHT_OCT32P',
].forEach( feature => {
if ( feature in featureTable.header ) {
console.warn( `I3DMLoader: Unsupported FeatureTable feature "${ feature }" detected.` );
}
} );
// use quantized position if position is missing
if ( ! POSITION && POSITION_QUANTIZED ) {
POSITION = new Float32Array( INSTANCES_LENGTH * 3 );
for ( let i = 0; i < INSTANCES_LENGTH; i ++ ) {
POSITION[ i * 3 + 0 ] = QUANTIZED_VOLUME_OFFSET[ 0 ] + ( POSITION_QUANTIZED[ i * 3 + 0 ] / 65535.0 ) * QUANTIZED_VOLUME_SCALE[ 0 ];
POSITION[ i * 3 + 1 ] = QUANTIZED_VOLUME_OFFSET[ 1 ] + ( POSITION_QUANTIZED[ i * 3 + 1 ] / 65535.0 ) * QUANTIZED_VOLUME_SCALE[ 1 ];
POSITION[ i * 3 + 2 ] = QUANTIZED_VOLUME_OFFSET[ 2 ] + ( POSITION_QUANTIZED[ i * 3 + 2 ] / 65535.0 ) * QUANTIZED_VOLUME_SCALE[ 2 ];
}
}
// get the average vector center so we can avoid floating point error due to lower
// precision transformation calculations on the GPU
const averageVector = new Vector3();
for ( let i = 0; i < INSTANCES_LENGTH; i ++ ) {
averageVector.x += POSITION[ i * 3 + 0 ] / INSTANCES_LENGTH;
averageVector.y += POSITION[ i * 3 + 1 ] / INSTANCES_LENGTH;
averageVector.z += POSITION[ i * 3 + 2 ] / INSTANCES_LENGTH;
}
// find all the children and create associated instance meshes
const instances = [];
const meshes = [];
model.scene.updateMatrixWorld();
model.scene.traverse( child => {
if ( child.isMesh ) {
meshes.push( child );
const { geometry, material } = child;
const instancedMesh = new InstancedMesh( geometry, material, INSTANCES_LENGTH );
instancedMesh.position.copy( averageVector );
if ( RTC_CENTER ) {
instancedMesh.position.x += RTC_CENTER[ 0 ];
instancedMesh.position.y += RTC_CENTER[ 1 ];
instancedMesh.position.z += RTC_CENTER[ 2 ];
}
instances.push( instancedMesh );
}
} );
// generate positions for all instances
for ( let i = 0; i < INSTANCES_LENGTH; i ++ ) {
// position
tempPos.set(
POSITION[ i * 3 + 0 ] - averageVector.x,
POSITION[ i * 3 + 1 ] - averageVector.y,
POSITION[ i * 3 + 2 ] - averageVector.z,
);
// rotation
tempQuat.identity();
// account for EAST_NORTH_UP per-instance below
if ( NORMAL_UP ) {
tempUp.set(
NORMAL_UP[ i * 3 + 0 ],
NORMAL_UP[ i * 3 + 1 ],
NORMAL_UP[ i * 3 + 2 ],
);
tempRight.set(
NORMAL_RIGHT[ i * 3 + 0 ],
NORMAL_RIGHT[ i * 3 + 1 ],
NORMAL_RIGHT[ i * 3 + 2 ],
);
tempFwd.crossVectors( tempRight, tempUp )
.normalize();
tempMat.makeBasis(
tempRight,
tempUp,
tempFwd,
);
tempQuat.setFromRotationMatrix( tempMat );
}
// scale
tempSca.set( 1, 1, 1 );
if ( SCALE_NON_UNIFORM ) {
tempSca.set(
SCALE_NON_UNIFORM[ i * 3 + 0 ],
SCALE_NON_UNIFORM[ i * 3 + 1 ],
SCALE_NON_UNIFORM[ i * 3 + 2 ],
);
}
if ( SCALE ) {
tempSca.multiplyScalar( SCALE[ i ] );
}
// multiple in the original meshes world transform
for ( let j = 0, l = instances.length; j < l; j ++ ) {
const instance = instances[ j ];
tempLocalQuat.copy( tempQuat );
// Handle east-north-up frame generation
if ( EAST_NORTH_UP ) {
instance.updateMatrixWorld();
// transform the instance position to global frame and get the rotation from the associated ENU frame.
tempGlobePos.copy( tempPos ).applyMatrix4( instance.matrixWorld );
this.ellipsoid.getPositionToCartographic( tempGlobePos, tempLatLon );
this.ellipsoid.getEastNorthUpFrame( tempLatLon.lat, tempLatLon.lon, tempEnuFrame );
tempLocalQuat.setFromRotationMatrix( tempEnuFrame );
}
tempMat.compose( tempPos, tempLocalQuat, tempSca ).multiply( adjustmentTransform );
const mesh = meshes[ j ];
tempMat2.multiplyMatrices( tempMat, mesh.matrixWorld );
instance.setMatrixAt( i, tempMat2 );
}
}
// replace all geometry with the instances
model.scene.clear();
model.scene.add( ...instances );
model.batchTable = batchTable;
model.featureTable = featureTable;
model.scene.batchTable = batchTable;
model.scene.featureTable = featureTable;
resolve( model );
}, reject );
} );
} );
}
}