Browse Source

Merge pull request #230 from matterport/extensions/3DTILES_content_gltf

3DTILES_content_gltf extension support
Garrett Johnson 3 years ago
parent
commit
3a3e1b9727

+ 30 - 0
example/gltf.html

@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+        <meta charset="utf-8"/>
+
+        <title>3D Tiles Renderer 3DTILES_content_gltf Example</title>
+
+        <style>
+            * {
+                margin: 0;
+                padding: 0;
+            }
+
+            html {
+                overflow: hidden;
+                font-family: Arial, Helvetica, sans-serif;
+                user-select: none;
+            }
+
+            canvas {
+                image-rendering: pixelated;
+                outline: none;
+            }
+        </style>
+    </head>
+    <body>
+        <script src="./gltf.js"></script>
+    </body>
+</html>

+ 146 - 0
example/gltf.js

@@ -0,0 +1,146 @@
+import { GLTFExtensionLoader } from '../src/three/GLTFExtensionLoader.js';
+import {
+	Scene,
+	DirectionalLight,
+	AmbientLight,
+	WebGLRenderer,
+	PerspectiveCamera,
+	sRGBEncoding,
+	PCFSoftShadowMap,
+} from 'three';
+import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+import { LoadingManager } from 'three';
+import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
+import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
+
+let camera, controls, scene, renderer;
+let dirLight;
+
+init();
+animate();
+
+function init() {
+
+	scene = new Scene();
+
+	// primary camera view
+	renderer = new WebGLRenderer( { antialias: true } );
+	renderer.setPixelRatio( window.devicePixelRatio );
+	renderer.setSize( window.innerWidth, window.innerHeight );
+	renderer.setClearColor( 0x151c1f );
+	renderer.shadowMap.enabled = true;
+	renderer.shadowMap.type = PCFSoftShadowMap;
+	renderer.outputEncoding = sRGBEncoding;
+
+	document.body.appendChild( renderer.domElement );
+
+	camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 );
+	camera.position.set( 3, 10, 20 );
+
+	// controls
+	controls = new OrbitControls( camera, renderer.domElement );
+	controls.screenSpacePanning = false;
+	controls.minDistance = 1;
+	controls.maxDistance = 2000;
+
+	// lights
+	dirLight = new DirectionalLight( 0xffffff, 1.25 );
+	dirLight.position.set( 1, 2, 3 ).multiplyScalar( 40 );
+	dirLight.castShadow = true;
+	dirLight.shadow.bias = - 0.01;
+	dirLight.shadow.mapSize.setScalar( 2048 );
+
+	const shadowCam = dirLight.shadow.camera;
+	shadowCam.left = - 200;
+	shadowCam.bottom = - 200;
+	shadowCam.right = 200;
+	shadowCam.top = 200;
+	shadowCam.updateProjectionMatrix();
+	scene.add( dirLight );
+
+	const ambLight = new AmbientLight( 0xffffff, 0.05 );
+	scene.add( ambLight );
+
+	// basic gltf test files
+	let insertPosition = 0;
+	const gltfModelTests = [
+		'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/CesiumMilkTruck/glTF-Binary/CesiumMilkTruck.glb',
+		'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/CesiumMilkTruck/glTF-Embedded/CesiumMilkTruck.gltf',
+		'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/CesiumMilkTruck/glTF/CesiumMilkTruck.gltf',
+	];
+
+	for ( const url of gltfModelTests ) {
+
+		const loader = new GLTFExtensionLoader();
+		loader.workingPath = loader.workingPathForURL( url );
+		loader.load( url )
+			.then( res => {
+
+				res.scene.position.set( insertPosition += 5, 0, 0 );
+				controls.target.set( insertPosition / 2, 0, 0 );
+				controls.update();
+				console.log( 'default loader:', { gltf: res, url } );
+				scene.add( res.scene );
+
+			} );
+
+	}
+
+	// gltf with extensions
+	const delegatedLoaderTests = [
+		'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/CesiumMilkTruck/glTF-Draco/CesiumMilkTruck.gltf',
+		'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Box/glTF-Binary/Box.glb',
+	];
+
+	const manager = new LoadingManager();
+	const gltfLoader = new GLTFLoader( manager );
+	const dracoLoader = new DRACOLoader( manager );
+	dracoLoader.setDecoderPath( 'https://unpkg.com/three@0.128.0/examples/js/libs/draco/gltf/' );
+	gltfLoader.setDRACOLoader( dracoLoader );
+	manager.addHandler( /\.gltf$/, gltfLoader );
+	manager.addHandler( /\.glb$/, gltfLoader );
+
+	for ( const url of delegatedLoaderTests ) {
+
+		const loader = new GLTFExtensionLoader( manager );
+		loader.workingPath = loader.workingPathForURL( url );
+		loader.load( url )
+			.then( res => {
+
+				res.scene.position.set( insertPosition += 5, 0, 0 );
+				controls.target.set( insertPosition / 2, 0, 0 );
+				controls.update();
+				console.log( 'custom loader:', { gltf: res, url } );
+				scene.add( res.scene );
+
+			} );
+
+	}
+
+	onWindowResize();
+	window.addEventListener( 'resize', onWindowResize, false );
+
+}
+
+function onWindowResize() {
+
+	camera.aspect = window.innerWidth / window.innerHeight;
+	renderer.setPixelRatio( window.devicePixelRatio );
+	renderer.setSize( window.innerWidth, window.innerHeight );
+	camera.updateProjectionMatrix();
+
+}
+
+function animate() {
+
+	requestAnimationFrame( animate );
+
+	render();
+
+}
+
+function render() {
+
+	renderer.render( scene, camera );
+
+}

+ 2 - 37
src/base/B3DMLoaderBase.js

@@ -2,44 +2,9 @@
 // https://github.com/CesiumGS/3d-tiles/blob/master/specification/TileFormats/Batched3DModel/README.md
 
 import { FeatureTable, BatchTable } from '../utilities/FeatureTable.js';
+import { LoaderBase } from './LoaderBase.js';
 
-export class B3DMLoaderBase {
-
-	constructor() {
-
-		this.fetchOptions = {};
-		this.workingPath = '';
-
-	}
-
-	load( url ) {
-
-		return fetch( url, this.fetchOptions )
-			.then( res => {
-
-				if ( ! res.ok ) {
-
-					throw new Error( `Failed to load file "${ url }" with status ${ res.status } : ${ res.statusText }` );
-
-				}
-				return res.arrayBuffer();
-
-			} )
-			.then( buffer => {
-
-				if ( this.workingPath === '' ) {
-
-					const splits = url.split( /\\\//g );
-					splits.pop();
-					this.workingPath = splits.join( '/' );
-
-				}
-
-				return this.parse( buffer );
-
-			} );
-
-	}
+export class B3DMLoaderBase extends LoaderBase {
 
 	parse( buffer ) {
 

+ 2 - 37
src/base/CMPTLoaderBase.js

@@ -1,43 +1,8 @@
 // CMPT File Format
 // https://github.com/CesiumGS/3d-tiles/blob/master/specification/TileFormats/Composite/README.md
+import { LoaderBase } from './LoaderBase.js';
 
-export class CMPTLoaderBase {
-
-	constructor() {
-
-		this.fetchOptions = {};
-		this.workingPath = '';
-
-	}
-
-	load( url ) {
-
-		return fetch( url, this.fetchOptions )
-			.then( res => {
-
-				if ( ! res.ok ) {
-
-					throw new Error( `Failed to load file "${ url }" with status ${ res.status } : ${ res.statusText }` );
-
-				}
-				return res.arrayBuffer();
-
-			} )
-			.then( buffer => {
-
-				if ( this.workingPath === '' ) {
-
-					const splits = url.split( /\\\//g );
-					splits.pop();
-					this.workingPath = splits.join( '/' );
-
-				}
-
-				return this.parse( buffer );
-
-			} );
-
-	}
+export class CMPTLoaderBase extends LoaderBase {
 
 	parse( buffer ) {
 

+ 2 - 51
src/base/I3DMLoaderBase.js

@@ -3,58 +3,9 @@
 
 import { FeatureTable, BatchTable } from '../utilities/FeatureTable.js';
 import { arrayToString } from '../utilities/arrayToString.js';
+import { LoaderBase } from './LoaderBase.js';
 
-export class I3DMLoaderBase {
-
-	constructor() {
-
-		this.fetchOptions = {};
-		this.workingPath = '';
-
-	}
-
-	resolveExternalURL( url ) {
-
-		if ( /^[^\\/]/.test( url ) ) {
-
-			return this.workingPath + '/' + url;
-
-		} else {
-
-			return url;
-
-		}
-
-	}
-
-	load( url ) {
-
-		return fetch( url, this.fetchOptions )
-			.then( res => {
-
-				if ( ! res.ok ) {
-
-					throw new Error( `Failed to load file "${ url }" with status ${ res.status } : ${ res.statusText }` );
-
-				}
-				return res.arrayBuffer();
-
-			} )
-			.then( buffer => {
-
-				if ( this.workingPath === '' ) {
-
-					const splits = url.split( /\\\//g );
-					splits.pop();
-					this.workingPath = splits.join( '/' );
-
-				}
-
-				return this.parse( buffer );
-
-			} );
-
-	}
+export class I3DMLoaderBase extends LoaderBase {
 
 	parse( buffer ) {
 

+ 66 - 0
src/base/LoaderBase.js

@@ -0,0 +1,66 @@
+export class LoaderBase {
+
+	constructor() {
+
+		this.fetchOptions = {};
+		this.workingPath = '';
+
+	}
+
+	load( url ) {
+
+		return fetch( url, this.fetchOptions )
+			.then( res => {
+
+				if ( ! res.ok ) {
+
+					throw new Error( `Failed to load file "${ url }" with status ${ res.status } : ${ res.statusText }` );
+
+				}
+				return res.arrayBuffer();
+
+			} )
+			.then( buffer => {
+
+				if ( this.workingPath === '' ) {
+
+					this.workingPath = this.workingPathForURL( url );
+
+				}
+
+				return this.parse( buffer );
+
+			} );
+
+	}
+
+	resolveExternalURL( url ) {
+
+		if ( /^[^\\/]/.test( url ) ) {
+
+			return this.workingPath + '/' + url;
+
+		} else {
+
+			return url;
+
+		}
+
+	}
+
+	workingPathForURL( url ) {
+
+		const splits = url.split( /[\\/]/g );
+		splits.pop();
+		const workingPath = splits.join( '/' );
+		return workingPath + '/';
+
+	}
+
+	parse( buffer ) {
+
+		throw new Error( 'LoaderBase: Parse not implemented.' );
+
+	}
+
+}

+ 1 - 1
src/base/PNTSLoaderBase.d.ts

@@ -11,6 +11,6 @@ export interface PNTSBaseResult {
 export class PNTSLoaderBase {
 
 	load( url : string ) : Promise< PNTSBaseResult >;
-	parse( buffer : ArrayBuffer ) : PNTSBaseResult;
+	parse( buffer : ArrayBuffer ) : Promise< PNTSBaseResult >;
 
 }

+ 6 - 26
src/base/PNTSLoaderBase.js

@@ -2,31 +2,9 @@
 // https://github.com/CesiumGS/3d-tiles/blob/master/specification/TileFormats/PointCloud/README.md
 
 import { FeatureTable, BatchTable } from "../utilities/FeatureTable.js";
+import { LoaderBase } from './LoaderBase.js';
 
-export class PNTSLoaderBase {
-
-	constructor() {
-
-		this.fetchOptions = {};
-
-	}
-
-	load( url ) {
-
-		return fetch( url, this.fetchOptions )
-			.then( res => {
-
-				if ( ! res.ok ) {
-
-					throw new Error( `Failed to load file "${ url }" with status ${ res.status } : ${ res.statusText }` );
-
-				}
-				return res.arrayBuffer();
-
-			} )
-			.then( buffer => this.parse( buffer ) );
-
-	}
+export class PNTSLoaderBase extends LoaderBase {
 
 	parse( buffer ) {
 
@@ -92,11 +70,13 @@ export class PNTSLoaderBase {
 			batchTableBinaryByteLength,
 		);
 
-		return {
+		return Promise.resolve( {
+
 			version,
 			featureTable,
 			batchTable,
-		};
+
+		} );
 
 	}
 

+ 21 - 42
src/index.d.ts

@@ -1,4 +1,4 @@
-import {
+export {
 	DebugTilesRenderer,
 	NONE,
 	SCREEN_ERROR,
@@ -8,47 +8,26 @@ import {
 	RELATIVE_DEPTH,
 	IS_LEAF,
 	RANDOM_COLOR,
+	RANDOM_NODE_COLOR,
+	CUSTOM_COLOR,
 } from './three/DebugTilesRenderer';
-import { TilesRenderer } from './three/TilesRenderer';
-import { TilesGroup } from './three/TilesGroup';
-import { B3DMLoader } from './three/B3DMLoader';
-import { I3DMLoader } from './three/I3DMLoader';
-import { PNTSLoader } from './three/PNTSLoader';
-import { CMPTLoader } from './three/CMPTLoader';
-
-import { TilesRendererBase } from './base/TilesRendererBase';
-import { Tile } from './base/Tile';
-import { TileBase } from './base/TileBase';
-import { Tileset } from './base/Tileset';
-import { B3DMLoaderBase } from './base/B3DMLoaderBase';
-import { I3DMLoaderBase } from './base/I3DMLoaderBase';
-import { PNTSLoaderBase } from './base/PNTSLoaderBase';
-import { CMPTLoaderBase } from './base/CMPTLoaderBase';
-
-import { LRUCache } from './utilities/LRUCache';
-import { PriorityQueue } from './utilities/PriorityQueue';
-
-export {
-	DebugTilesRenderer,
-	TilesRenderer,
-	B3DMLoader,
+export { TilesRenderer } from './three/TilesRenderer';
+export { TilesGroup } from './three/TilesGroup';
+export { B3DMLoader } from './three/B3DMLoader';
+export { I3DMLoader } from './three/I3DMLoader';
+export { PNTSLoader } from './three/PNTSLoader';
+export { CMPTLoader } from './three/CMPTLoader';
+export { GLTFExtensionLoader } from './three/GLTFExtensionLoader';
 
-	Tile,
-	TileBase,
-	Tileset,
-	TilesRendererBase,
-	TilesGroup,
-	B3DMLoaderBase,
+export { TilesRendererBase } from './base/TilesRendererBase';
+export { Tile } from './base/Tile';
+export { TileBase } from './base/TileBase';
+export { Tileset } from './base/Tileset';
+export { B3DMLoaderBase } from './base/B3DMLoaderBase';
+export { I3DMLoaderBase } from './base/I3DMLoaderBase';
+export { PNTSLoaderBase } from './base/PNTSLoaderBase';
+export { CMPTLoaderBase } from './base/CMPTLoaderBase';
+export { LoaderBase } from './base/LoaderBase';
 
-	LRUCache,
-	PriorityQueue,
-
-	NONE,
-	SCREEN_ERROR,
-	GEOMETRIC_ERROR,
-	DISTANCE,
-	DEPTH,
-	RELATIVE_DEPTH,
-	IS_LEAF,
-	RANDOM_COLOR,
-};
+export { LRUCache } from './utilities/LRUCache';
+export { PriorityQueue } from './utilities/PriorityQueue';

+ 4 - 0
src/index.js

@@ -16,8 +16,10 @@ import { B3DMLoader } from './three/B3DMLoader.js';
 import { PNTSLoader } from './three/PNTSLoader.js';
 import { I3DMLoader } from './three/I3DMLoader.js';
 import { CMPTLoader } from './three/CMPTLoader.js';
+import { GLTFExtensionLoader } from './three/GLTFExtensionLoader.js';
 
 import { TilesRendererBase } from './base/TilesRendererBase.js';
+import { LoaderBase } from './base/LoaderBase.js';
 import { B3DMLoaderBase } from './base/B3DMLoaderBase.js';
 import { I3DMLoaderBase } from './base/I3DMLoaderBase.js';
 import { PNTSLoaderBase } from './base/PNTSLoaderBase.js';
@@ -33,8 +35,10 @@ export {
 	PNTSLoader,
 	I3DMLoader,
 	CMPTLoader,
+	GLTFExtensionLoader,
 
 	TilesRendererBase,
+	LoaderBase,
 	B3DMLoaderBase,
 	I3DMLoaderBase,
 	PNTSLoaderBase,

+ 1 - 1
src/three/B3DMLoader.d.ts

@@ -20,6 +20,6 @@ export class B3DMLoader {
 
 	constructor( manager : LoadingManager );
 	load( url : String ) : Promise< B3DMResult >;
-	parse( buffer : ArrayBuffer ) : B3DMResult;
+	parse( buffer : ArrayBuffer ) : Promise < B3DMResult >;
 
 }

+ 1 - 1
src/three/CMPTLoader.d.ts

@@ -14,6 +14,6 @@ export class CMPTLoader {
 	
 	constructor( manager : LoadingManager );
 	load( url : String ) : Promise< CMPTResult >;
-	parse( buffer : ArrayBuffer ) : CMPTResult;
+	parse( buffer : ArrayBuffer ) : Promise< CMPTResult >;
 
 }

+ 4 - 2
src/three/CMPTLoader.js

@@ -40,8 +40,10 @@ export class CMPTLoader extends CMPTLoaderBase {
 				case 'pnts': {
 
 					const slicedBuffer = buffer.slice();
-					const pointsResult = new PNTSLoader( manager ).parse( slicedBuffer.buffer );
-					const promise = Promise.resolve( pointsResult );
+					const loader = new PNTSLoader( manager );
+					loader.workingPath = this.workingPath;
+					loader.fetchOptions = this.fetchOptions;
+					const promise = loader.parse( slicedBuffer.buffer );
 					promises.push( promise );
 					break;
 

+ 11 - 0
src/three/GLTFExtensionLoader.d.ts

@@ -0,0 +1,11 @@
+
+import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js';
+import { LoaderBase } from '../base/LoaderBase';
+import { LoadingManager } from 'three';
+
+export class GLTFExtensionLoader extends LoaderBase {
+
+	constructor( manager : LoadingManager );
+	parse( buffer : ArrayBuffer ) : Promise< GLTF >;
+
+}

+ 60 - 0
src/three/GLTFExtensionLoader.js

@@ -0,0 +1,60 @@
+import { DefaultLoadingManager } from 'three';
+import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
+import { LoaderBase } from '../base/LoaderBase.js';
+
+export class GLTFExtensionLoader extends LoaderBase {
+
+	constructor( manager = DefaultLoadingManager ) {
+
+		super();
+		this.manager = manager;
+
+	}
+
+	parse( buffer ) {
+
+		return new Promise( ( resolve, reject ) => {
+
+			const manager = this.manager;
+			const fetchOptions = this.fetchOptions;
+			let loader = manager.getHandler( 'path.gltf' ) || manager.getHandler( 'path.glb' );
+
+			if ( ! loader ) {
+
+				loader = 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 );
+
+				}
+
+
+			}
+
+			// assume any pre-registered loader has paths configured as the user desires, but if we're making
+			// a new loader, use the working path during parse to support relative uris on other hosts
+			const resourcePath = loader.resourcePath || loader.path || this.workingPath;
+
+			loader.parse( buffer, resourcePath, model => {
+
+				resolve( model );
+
+			}, reject );
+
+		} );
+
+	}
+
+}

+ 1 - 1
src/three/I3DMLoader.d.ts

@@ -20,6 +20,6 @@ export class I3DMLoader {
 
 	constructor( manager : LoadingManager );
 	load( url : String ) : Promise< I3DMResult >;
-	parse( buffer : ArrayBuffer ) : I3DMResult;
+	parse( buffer : ArrayBuffer ) : Promise< I3DMResult >;
 	
 }

+ 3 - 3
src/three/PNTSLoader.d.ts

@@ -1,4 +1,4 @@
-import { PNTSBaseResult } from '../base/PNTSLoaderBase';
+import { PNTSBaseResult, PNTSLoaderBase } from '../base/PNTSLoaderBase';
 import { FeatureTable } from '../utilities/FeatureTable';
 import { Points, LoadingManager } from 'three';
 
@@ -14,10 +14,10 @@ export interface PNTSResult extends PNTSBaseResult {
 
 }
 
-export class PNTSLoader {
+export class PNTSLoader extends PNTSLoaderBase {
 
 	constructor( manager : LoadingManager );
 	load( url : String ) : Promise< PNTSResult >;
-	parse( buffer : ArrayBuffer ) : PNTSResult;
+	parse( buffer : ArrayBuffer ) : Promise< PNTSResult >;
 
 }

+ 45 - 40
src/three/PNTSLoader.js

@@ -12,63 +12,68 @@ export class PNTSLoader extends PNTSLoaderBase {
 
 	parse( buffer ) {
 
-		const result = super.parse( buffer );
-		const { featureTable } = result;
+		return super
+			.parse( buffer )
+			.then( result => {
 
-		const POINTS_LENGTH = featureTable.getData( 'POINTS_LENGTH' );
-		const POSITION = featureTable.getData( 'POSITION', POINTS_LENGTH, 'FLOAT', 'VEC3' );
-		const RGB = featureTable.getData( 'RGB', POINTS_LENGTH, 'UNSIGNED_BYTE', 'VEC3' );
+				const { featureTable } = result;
 
-		[
-			'RTC_CENTER',
-			'QUANTIZED_VOLUME_OFFSET',
-			'QUANTIZED_VOLUME_SCALE',
-			'CONSTANT_RGBA',
-			'BATCH_LENGTH',
-			'POSITION_QUANTIZED',
-			'RGBA',
-			'RGB565',
-			'NORMAL',
-			'NORMAL_OCT16P',
-		].forEach( feature => {
+				const POINTS_LENGTH = featureTable.getData( 'POINTS_LENGTH' );
+				const POSITION = featureTable.getData( 'POSITION', POINTS_LENGTH, 'FLOAT', 'VEC3' );
+				const RGB = featureTable.getData( 'RGB', POINTS_LENGTH, 'UNSIGNED_BYTE', 'VEC3' );
 
-			if ( feature in featureTable.header ) {
+				[
+					'RTC_CENTER',
+					'QUANTIZED_VOLUME_OFFSET',
+					'QUANTIZED_VOLUME_SCALE',
+					'CONSTANT_RGBA',
+					'BATCH_LENGTH',
+					'POSITION_QUANTIZED',
+					'RGBA',
+					'RGB565',
+					'NORMAL',
+					'NORMAL_OCT16P',
+				].forEach( feature => {
 
-				console.warn( `PNTSLoader: Unsupported FeatureTable feature "${ feature }" detected.` );
+					if ( feature in featureTable.header ) {
 
-			}
+						console.warn( `PNTSLoader: Unsupported FeatureTable feature "${ feature }" detected.` );
 
-		} );
+					}
 
-		const geometry = new BufferGeometry();
-		geometry.setAttribute( 'position', new BufferAttribute( POSITION, 3, false ) );
+				} );
 
-		const material = new PointsMaterial();
-		material.size = 2;
-		material.sizeAttenuation = false;
+				const geometry = new BufferGeometry();
+				geometry.setAttribute( 'position', new BufferAttribute( POSITION, 3, false ) );
 
-		if ( RGB !== null ) {
+				const material = new PointsMaterial();
+				material.size = 2;
+				material.sizeAttenuation = false;
 
-			geometry.setAttribute( 'color', new BufferAttribute( RGB, 3, true ) );
-			material.vertexColors = true;
+				if ( RGB !== null ) {
 
-		}
+					geometry.setAttribute( 'color', new BufferAttribute( RGB, 3, true ) );
+					material.vertexColors = true;
 
-		const object = new Points( geometry, material );
-		result.scene = object;
-		result.scene.featureTable = featureTable;
+				}
 
-		const rtcCenter = featureTable.getData( 'RTC_CENTER' );
+				const object = new Points( geometry, material );
+				result.scene = object;
+				result.scene.featureTable = featureTable;
 
-		if ( rtcCenter ) {
+				const rtcCenter = featureTable.getData( 'RTC_CENTER' );
 
-			result.scene.position.x += rtcCenter[ 0 ];
-			result.scene.position.y += rtcCenter[ 1 ];
-			result.scene.position.z += rtcCenter[ 2 ];
+				if ( rtcCenter ) {
 
-		}
+					result.scene.position.x += rtcCenter[ 0 ];
+					result.scene.position.y += rtcCenter[ 1 ];
+					result.scene.position.z += rtcCenter[ 2 ];
 
-		return result;
+				}
+
+				return result;
+
+			} );
 
 	}
 

+ 1 - 2
src/three/TilesRenderer.d.ts

@@ -13,7 +13,7 @@ export class TilesRenderer extends TilesRendererBase {
 
 	group : TilesGroup;
 
-	getBoundsTransform(target: Matrix4) : Boolean;
+	getBoundsTransform( target: Matrix4 ) : Boolean;
 
 	getBounds( box : Box3 ) : Boolean;
 
@@ -32,5 +32,4 @@ export class TilesRenderer extends TilesRendererBase {
 	onDisposeModel : ( ( scene : Object3D, tile : Tile ) => void ) | null;
 	onTileVisibilityChange : ( ( scene : Object3D, tile : Tile, visible : boolean ) => void ) | null;
 
-
 }

+ 26 - 5
src/three/TilesRenderer.js

@@ -3,6 +3,7 @@ import { B3DMLoader } from './B3DMLoader.js';
 import { PNTSLoader } from './PNTSLoader.js';
 import { I3DMLoader } from './I3DMLoader.js';
 import { CMPTLoader } from './CMPTLoader.js';
+import { GLTFExtensionLoader } from './GLTFExtensionLoader.js';
 import { TilesGroup } from './TilesGroup.js';
 import {
 	Matrix4,
@@ -582,7 +583,8 @@ export class TilesRenderer extends TilesRendererBase {
 
 		switch ( extension ) {
 
-			case 'b3dm':
+			case 'b3dm': {
+
 				const loader = new B3DMLoader( manager );
 				loader.workingPath = workingPath;
 				loader.fetchOptions = fetchOptions;
@@ -591,10 +593,20 @@ export class TilesRenderer extends TilesRendererBase {
 					.then( res => res.scene );
 				break;
 
-			case 'pnts':
-				promise = Promise.resolve( new PNTSLoader( manager ).parse( buffer ).scene );
+			}
+
+			case 'pnts': {
+
+				const loader = new PNTSLoader( manager );
+				loader.workingPath = workingPath;
+				loader.fetchOptions = fetchOptions;
+				promise = loader
+					.parse( buffer )
+					.then( res => res.scene );
 				break;
 
+			}
+
 			case 'i3dm': {
 
 				const loader = new I3DMLoader( manager );
@@ -603,7 +615,6 @@ export class TilesRenderer extends TilesRendererBase {
 				promise = loader
 					.parse( buffer )
 					.then( res => res.scene );
-
 				break;
 
 			}
@@ -616,11 +627,21 @@ export class TilesRenderer extends TilesRendererBase {
 				promise = loader
 					.parse( buffer )
 					.then( res => res.scene	);
-
 				break;
 
 			}
 
+			// 3DTILES_content_gltf
+			case 'gltf':
+			case 'glb':
+				const loader = new GLTFExtensionLoader( manager );
+				loader.workingPath = workingPath;
+				loader.fetchOptions = fetchOptions;
+				promise = loader
+					.parse( buffer )
+					.then( res => res.scene	);
+				break;
+
 			default:
 				console.warn( `TilesRenderer: Content type "${ extension }" not supported.` );
 				promise = Promise.resolve( null );