Forráskód Böngészése

Merge pull request #98 from NASA-AMMOS/updates

Some more small fixes
Garrett Johnson 5 éve
szülő
commit
b9dea15a9e

+ 11 - 0
CHANGELOG.md

@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## Unreleased
+
+### Added
+
+- `TilesRenderer.dispose` function to completely dispose of all loaded geometry, materials, and textures in the scene when the renderer is no longer needed.
+- `TilesRenderer.onDisposeModel` function which is called when a tile model is disposed of from the cache.
+
+### Fixed
+
+- Case where the url protocol was converted to use a single slash instead of two when loading a model.
+
 ## [0.1.3] - 2020-07-12
 ### Added
 

+ 35 - 3
README.md

@@ -63,18 +63,34 @@ tilesRenderer.setCamera( camera );
 tilesRenderer.setResolutionFromRenderer( camera, renderer );
 tilesRenderer.onLoadModel = function ( scene ) {
 
+	// create a custom material for the tile
 	scene.traverse( c => {
 
 		if ( c.material ) {
 
-			c.originalMaterial = material;
+			c.originalMaterial = c.material;
 			c.material = new MeshBasicMaterial();
 
 		}
 
 	} );
 
-}
+};
+
+tilesRenderer.onDisposeModel = function ( scene ) {
+
+	// dispose of any manually created materials
+	scene.traverse( c => {
+
+		if ( c.material ) {
+
+			c.material.dispose();
+
+		}
+
+	} );
+
+};
 scene.add( tilesRenderer.group );
 ```
 
@@ -249,11 +265,27 @@ Fires the callback for every loaded scene in the hierarchy with the associatd ti
 ### .onLoadModel
 
 ```js
-onLoadModel = null : ( scene : Object3D, tile : objec ) => void
+onLoadModel = null : ( scene : Object3D, tile : object ) => void
 ```
 
 Callback that is called every time a model is loaded. This can be used in conjunction with [.forEachLoadedModel](#forEachLoadedModel) to set the material of all load and still yet to load meshes in the tile set.
 
+### .onDisposeModel
+
+```js
+onDisposeModel = null : ( scene : Object3D, tile : object ) => void
+```
+
+Callback that is called every time a model is disposed of. This should be used in conjunction with [.onLoadModel](#onLoadModel) to dispose of any custom materials created for a tile. Note that the textures, materials, and geometries that a tile loaded in with are all automatically disposed of even if they have been removed from the tile meshes.
+
+### .dispose
+
+```js
+dispose() : void
+```
+
+Disposes of all the tiles in the renderer. Calls dispose on all materials, textures, and geometries that were loaded by the renderer and subsequently calls [onDisposeModel](#onDisposeModel) for any loaded tile model.
+
 ## DebugTilesRenderer
 
 _extends [TilesRenderer](#TilesRenderer)_

+ 53 - 8
example/customMaterial.js

@@ -18,7 +18,7 @@ import * as dat from 'three/examples/jsm/libs/dat.gui.module.js';
 import Stats from 'three/examples/jsm/libs/stats.module.js';
 
 let camera, controls, scene, renderer, tiles, orthoCamera;
-let offsetParent, box, dirLight;
+let offsetParent, box, dirLight, statsContainer;
 let stats;
 
 const DEFAULT = 0;
@@ -29,6 +29,7 @@ const params = {
 
 	'material': DEFAULT,
 	'orthographic': false,
+	'rebuild': initTiles,
 
 };
 
@@ -179,6 +180,38 @@ function onLoadModel( scene ) {
 
 }
 
+function onDisposeModel( scene ) {
+
+	scene.traverse( c => {
+
+		if ( c.isMesh ) {
+
+			c.material.dispose();
+
+		}
+
+	} );
+
+}
+
+function initTiles() {
+
+	if ( tiles ) {
+
+		tiles.group.parent.remove( tiles.group );
+		tiles.dispose();
+
+	}
+
+	const url = window.location.hash.replace( /^#/, '' ) || '../data/tileset.json';
+	tiles = new TilesRenderer( url );
+	tiles.errorTarget = 2;
+	tiles.onLoadModel = onLoadModel;
+	tiles.onDisposeModel = onDisposeModel;
+	offsetParent.add( tiles.group );
+
+}
+
 function init() {
 
 	scene = new Scene();
@@ -229,13 +262,7 @@ function init() {
 	offsetParent = new Group();
 	scene.add( offsetParent );
 
-	// tiles
-	const url = window.location.hash.replace( /^#/, '' ) || '../data/tileset.json';
-	tiles = new TilesRenderer( url );
-	tiles.errorTarget = 2;
-	tiles.onLoadModel = onLoadModel;
-	offsetParent.add( tiles.group );
-
+	initTiles();
 
 	onWindowResize();
 	window.addEventListener( 'resize', onWindowResize, false );
@@ -250,6 +277,7 @@ function init() {
 			tiles.forEachLoadedModel( updateMaterial )
 
 		} );
+	gui.add( params, 'rebuild' );
 	gui.open();
 
 	// Stats
@@ -257,6 +285,18 @@ function init() {
 	stats.showPanel( 0 );
 	document.body.appendChild( stats.dom );
 
+	statsContainer = document.createElement( 'div' );
+	statsContainer.style.position = 'absolute';
+	statsContainer.style.top = 0;
+	statsContainer.style.left = 0;
+	statsContainer.style.color = 'white';
+	statsContainer.style.width = '100%';
+	statsContainer.style.textAlign = 'center';
+	statsContainer.style.padding = '5px';
+	statsContainer.style.pointerEvents = 'none';
+	statsContainer.style.lineHeight = '1.5em';
+	document.body.appendChild( statsContainer );
+
 }
 
 function onWindowResize() {
@@ -336,6 +376,11 @@ function render() {
 
 	updateOrthoCamera();
 
+	statsContainer.innerText =
+		`Geometries: ${ renderer.info.memory.geometries } ` +
+		`Textures: ${ renderer.info.memory.textures } ` +
+		`Programs: ${ renderer.info.programs.length } `;
+
 	renderer.render( scene, params.orthographic ? orthoCamera : camera );
 
 }

+ 11 - 0
example/index.js

@@ -49,6 +49,7 @@ let params = {
 	'enableUpdate': true,
 	'raycast': NONE,
 	'enableCacheDisplay': false,
+	'enableRendererStats': false,
 	'orthographic': false,
 
 	'errorTarget': 6,
@@ -77,6 +78,7 @@ function reinstantiateTiles() {
 	if ( tiles ) {
 
 		offsetParent.remove( tiles.group );
+		tiles.dispose();
 
 	}
 
@@ -254,6 +256,7 @@ function init() {
 	} );
 	exampleOptions.add( params, 'raycast', { NONE, ALL_HITS, FIRST_HIT_ONLY } );
 	exampleOptions.add( params, 'enableCacheDisplay' );
+	exampleOptions.add( params, 'enableRendererStats' );
 	exampleOptions.open();
 
 	gui.add( params, 'reload' );
@@ -620,6 +623,14 @@ function render() {
 
 	}
 
+	if ( params.enableRendererStats ) {
+
+		const memory = renderer.info.memory;
+		const programCount = renderer.info.programs.length;
+		str += `<br/>Geometries: ${ memory.geometries } Textures: ${ memory.textures } Programs: ${ programCount }`;
+
+	}
+
 	if ( statsContainer.innerHTML !== str ) {
 
 		statsContainer.innerHTML = str;

+ 15 - 0
example/offscreenShadows.js

@@ -50,6 +50,20 @@ function onLoadModel( scene ) {
 
 }
 
+function onDisposeModel( scene ) {
+
+	scene.traverse( c => {
+
+		if ( c.isMesh ) {
+
+			c.material.dispose();
+
+		}
+
+	} );
+
+}
+
 function init() {
 
 	scene = new Scene();
@@ -104,6 +118,7 @@ function init() {
 	const url = window.location.hash.replace( /^#/, '' ) || '../data/tileset.json';
 	tiles = new TilesRenderer( url );
 	tiles.onLoadModel = onLoadModel;
+	tiles.onDisposeModel = onDisposeModel;
 	offsetParent.add( tiles.group );
 
 

+ 1 - 0
src/base/TilesRendererBase.d.ts

@@ -24,5 +24,6 @@ export class TilesRendererBase {
 		beforeCb : ( ( tile : Object, parent : Object, depth : Number ) => Boolean ) | null,
 		afterCb : ( ( tile : Object, parent : Object, depth : Number ) => Boolean ) | null
 	) : void;
+	dispose() : void;
 
 }

+ 13 - 1
src/base/TilesRendererBase.js

@@ -1,4 +1,5 @@
 import path from 'path';
+import { urlJoin } from '../utilities/urlJoin.js';
 import { LRUCache } from '../utilities/LRUCache.js';
 import { PriorityQueue } from '../utilities/PriorityQueue.js';
 import { determineFrustumSet, toggleTiles, skipTraversal, markUsedSetLeaves, traverseSet } from './traverseFunctions.js';
@@ -142,7 +143,7 @@ export class TilesRendererBase {
 
 			if ( tile.content.uri ) {
 
-				tile.content.uri = path.join( tileSetDir, tile.content.uri );
+				tile.content.uri = urlJoin( tileSetDir, tile.content.uri );
 
 			}
 
@@ -463,4 +464,15 @@ export class TilesRendererBase {
 
 	}
 
+	dispose() {
+
+		const lruCache = this.lruCache;
+		this.traverse( tile => {
+
+			lruCache.remove( tile );
+
+		} );
+
+	}
+
 }

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

@@ -19,6 +19,7 @@ export class TilesRenderer extends TilesRendererBase {
 	setResolutionFromRenderer( camera : Camera, renderer : WebGLRenderer ) : Boolean;
 
 	onLoadModel : ( ( scene : Object3D, tile : object ) => void ) | null;
+	onDisposeModel : ( ( scene : Object3D, tile : object ) => void ) | null;
 	forEachLoadedModel( callback : ( scene : Object3D, tile : object ) => void );
 
 }

+ 7 - 0
src/three/TilesRenderer.js

@@ -83,6 +83,7 @@ export class TilesRenderer extends TilesRendererBase {
 		this._autoDisableRendererCulling = true;
 
 		this.onLoadModel = null;
+		this.onDisposeModel = null;
 
 	}
 
@@ -621,6 +622,12 @@ export class TilesRenderer extends TilesRendererBase {
 
 			}
 
+			if ( this.onDisposeModel ) {
+
+				this.onDisposeModel( cached.scene, tile );
+
+			}
+
 			cached.scene = null;
 			cached.materials = null;
 			cached.textures = null;

+ 33 - 0
src/utilities/urlJoin.js

@@ -0,0 +1,33 @@
+import path from 'path';
+
+// Function that properly handles path resolution for parts that have
+// a protocol component like "http://".
+export function urlJoin( ...args ) {
+
+	const protocolRegex = /^[a-zA-Z]+:\/\//;
+	let lastRoot = - 1;
+	for ( let i = 0, l = args.length; i < l; i ++ ) {
+
+		if ( protocolRegex.test( args[ i ] ) ) {
+
+			lastRoot = i;
+
+		}
+
+	}
+
+	if ( lastRoot === - 1 ) {
+
+		return path.join( ...args ).replace( /\\/g, '/' );
+
+	} else {
+
+		const parts = lastRoot <= 0 ? args : args.slice( lastRoot );
+		const protocol = parts[ 0 ].match( protocolRegex )[ 0 ];
+		parts[ 0 ] = parts[ 0 ].substring( protocol.length );
+
+		return ( protocol + path.join( ...parts ) ).replace( /\\/g, '/' );
+
+	}
+
+}

+ 41 - 0
test/urlJoin.test.js

@@ -0,0 +1,41 @@
+import { urlJoin } from '../src/utilities/urlJoin.js';
+
+describe( 'urlJoin', () => {
+
+	it( 'should behave like path.join if no protocol is present', () => {
+
+		expect(
+			urlJoin( 'path', 'to', 'file.json' )
+		).toBe( 'path/to/file.json' );
+
+		expect(
+			urlJoin( 'path//', 'to/other/', 'file.json' )
+		).toBe( 'path/to/other/file.json' );
+
+	} );
+
+	it( 'should handle protocols correctly.', () => {
+
+		expect(
+			urlJoin( 'http://path', 'to', 'file.json' )
+		).toBe( 'http://path/to/file.json' );
+
+		expect(
+			urlJoin( 'http://path', 'http://path2', 'to', 'file.json' )
+		).toBe( 'http://path2/to/file.json' );
+
+		expect(
+			urlJoin( 'https://path', 'to', 'file.json' )
+		).toBe( 'https://path/to/file.json' );
+
+		expect(
+			urlJoin( 'ftp://path', 'to', 'file.json' )
+		).toBe( 'ftp://path/to/file.json' );
+
+		expect(
+			urlJoin( 'ftp://http://path', 'to', 'file.json' )
+		).toBe( 'ftp://http:/path/to/file.json' );
+
+	} );
+
+} );