Explorar o código

Merge branch 'master' into raycast

# Conflicts:
#	example/index.js
#	src/ThreeTilesRenderer.js
Garrett Johnson %!s(int64=5) %!d(string=hai) anos
pai
achega
24e8b62e77

+ 79 - 34
example/bundle/example.e31bb0bc.js

@@ -1111,7 +1111,13 @@ function toggleTiles(tile, renderer) {
 
 class TilesRenderer {
   get root() {
-    return this.tileSets[this.rootSet].root;
+    const tileSet = this.tileSets[this.rootSet];
+
+    if (!tileSet || tileSet instanceof Promise) {
+      return null;
+    } else {
+      return tileSet.root;
+    }
   }
 
   constructor(url, cache = new _LRUCache.LRUCache(), downloadQueue = new _PriorityQueue.PriorityQueue(6), parseQueue = new _PriorityQueue.PriorityQueue(2)) {
@@ -1142,26 +1148,18 @@ class TilesRenderer {
   traverse(cb) {
     const tileSets = this.tileSets;
     const rootTileSet = tileSets[this.rootSet];
-    if (!rootTileSet.root) return;
+    if (!rootTileSet || !rootTileSet.root) return;
     traverseSet(rootTileSet.root, cb);
   } // Public API
 
 
   update() {
-    // TODO: Mark all tiles as unused
-    // TODO: Check if any tiles have finished
     const stats = this.stats;
     const lruCache = this.lruCache;
     const tileSets = this.tileSets;
     const rootTileSet = tileSets[this.rootSet];
-    if (!rootTileSet.root) return;
-    const root = rootTileSet.root; // TODO: determine if the root tile should be visible / rendered?
-    // TODO: Preprocess and modify the node uris instead of
-    // generating the path on the fly
-    // const errorTarget = this.errorTarget;
-    // const errorThreshold = this.errorThreshold;
-    // const maxDepth = this.maxDepth;
-
+    if (!rootTileSet || !rootTileSet.root) return;
+    const root = rootTileSet.root;
     stats.inFrustum = 0, stats.used = 0, stats.active = 0, stats.visible = 0, this.frameCount++;
     determineFrustumSet(root, this);
     markUsedSetLeaves(root, this);
@@ -1197,6 +1195,7 @@ class TilesRenderer {
     }
 
     tile.parent = parentTile;
+    tile.children = tile.children || [];
     tile.__contentEmpty = !tile.content || !tile.content.uri;
     tile.__error = 0.0;
     tile.__inFrustum = false;
@@ -1239,7 +1238,11 @@ class TilesRenderer {
       const pr = fetch(url, {
         credentials: 'same-origin'
       }).then(res => {
-        return res.json();
+        if (res.ok) {
+          return res.json();
+        } else {
+          throw new Error(`Status ${res.status} (${res.statusText})`);
+        }
       }).then(json => {
         // TODO: Add version query?
         const version = json.asset.version;
@@ -1248,8 +1251,9 @@ class TilesRenderer {
         const basePath = _path.default.dirname(url);
 
         traverseSet(json.root, (node, parent) => this.preprocessNode(node, parent, basePath));
-        tileSets[url] = json;
-        return json;
+        tileSets[url] = json; // TODO: schedule an update to avoid doing this too many times
+
+        this.update();
       });
       pr.catch(e => {
         console.error(`TilesLoader: Failed to load tile set json "${url}"`);
@@ -38110,10 +38114,13 @@ const tempVector = new _three.Vector3();
 const resVector = new _three.Vector2();
 const vecX = new _three.Vector3();
 const vecY = new _three.Vector3();
-const vecZ = new _three.Vector3(); // Specialization of "Group" that only updates world matrices of children if
+const vecZ = new _three.Vector3();
+
+const _sphere = new _three.Sphere(); // Specialization of "Group" that only updates world matrices of children if
 // the transform has changed since the last update and ignores the "force"
 // parameter under the assumption that the children tiles will not move.
 
+
 class TilesGroup extends _three.Group {
   constructor(tilesRenderer) {
     super();
@@ -38208,9 +38215,13 @@ class ThreeTilesRenderer extends _TilesRenderer.TilesRenderer {
       return false;
     }
 
-    const d = this.root.boundingVolume.box;
-    box.min.set(d[0] - d[3], d[1] - d[7], d[2] - d[11]);
-    box.max.set(d[0] + d[3], d[1] + d[7], d[2] + d[11]);
+    const cached = this.root.cached;
+    const boundingBox = cached.box;
+    const obbMat = cached.boxTransform;
+    const transformMat = cached.transform;
+    box.copy(boundingBox);
+    tempMat.multiplyMatrices(transformMat, obbMat);
+    box.applyMatrix4(tempMat);
     return true;
   }
   /* Overriden */
@@ -38228,7 +38239,7 @@ class ThreeTilesRenderer extends _TilesRenderer.TilesRenderer {
 
     while (frustums.length < cameras.length) {
       frustums.push(new _three.Frustum());
-    } // store the camera frustums
+    } // store the camera frustums in the 3d tiles root frame
 
 
     for (let i = 0, l = frustums.length; i < l; i++) {
@@ -38260,6 +38271,10 @@ class ThreeTilesRenderer extends _TilesRenderer.TilesRenderer {
       transform.identity();
     }
 
+    if (parentTile) {
+      transform.multiply(parentTile.cached.transform);
+    }
+
     let box = null;
     let boxTransform = null;
 
@@ -38403,24 +38418,24 @@ class ThreeTilesRenderer extends _TilesRenderer.TilesRenderer {
 
   calculateError(tile) {
     const cached = tile.cached;
-    const cameras = this.cameras;
-    const group = this.group; // TODO: Use the content bounding volume here?
+    const cameras = this.cameras; // TODO: Use the content bounding volume here?
 
     const boundingVolume = tile.boundingVolume;
-    const groupMatrixWorld = group.matrixWorld;
     const transformMat = cached.transform;
 
     if ('box' in boundingVolume) {
+      const group = this.group;
       const boundingBox = cached.box;
       const obbMat = cached.boxTransform; // TODO: these can likely be cached? Or the world transform mat can be used
       // transformMat can be rolled into oobMat
 
-      tempMat.multiplyMatrices(transformMat, obbMat);
-      tempMat.premultiply(groupMatrixWorld);
+      tempMat.copy(transformMat);
+      tempMat.multiply(obbMat);
       tempMat.getInverse(tempMat);
       let minError = Infinity;
 
       for (let i = 0, l = cameras.length; i < l; i++) {
+        // transform camera position into local frame of the tile bounding box
         const cam = cameras[i];
         tempVector.copy(cam.position);
         tempVector.applyMatrix4(tempMat);
@@ -38432,9 +38447,23 @@ class ThreeTilesRenderer extends _TilesRenderer.TilesRenderer {
           const pixelSize = Math.Max(h, w) / Math.Max(resVector.width, resVector.height);
           error = tile.geometricError / pixelSize;
         } else {
-          const distance = boundingBox.distanceToPoint(tempVector);
+          const distance = boundingBox.distanceToPoint(tempVector); // assume the scales on all axes are uniform.
+
+          let scale; // account for tile scale.
+
+          tempVector.setFromMatrixScale(tempMat);
+          scale = tempVector.x; // account for parent group scale. Divide because this matrix has not been inverted like the previous one.
+
+          tempVector.setFromMatrixScale(group.matrixWorld);
+          scale /= tempVector.x;
+
+          if (Math.abs(Math.max(scale.x - scale.y, scale.x - scale.z)) > 1e-6) {
+            console.warn('ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when claculating screen space error.');
+          }
+
+          const scaledDistance = distance * scale;
           const sseDenominator = 2 * Math.tan(0.5 * cam.fov * DEG2RAD);
-          error = tile.geometricError * resVector.height / (distance * sseDenominator);
+          error = tile.geometricError * resVector.height / (scaledDistance * sseDenominator);
         }
 
         minError = Math.min(minError, error);
@@ -38458,12 +38487,16 @@ class ThreeTilesRenderer extends _TilesRenderer.TilesRenderer {
     const sphere = tile.cached.sphere;
 
     if (sphere) {
+      _sphere.copy(sphere);
+
+      _sphere.applyMatrix4(tile.cached.transform);
+
       const frustums = this.frustums;
 
       for (let i = 0, l = frustums.length; i < l; i++) {
         const frustum = frustums[i];
 
-        if (frustum.intersectsSphere(sphere)) {
+        if (frustum.intersectsSphere(_sphere)) {
           return true;
         }
       }
@@ -42335,12 +42368,14 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj;
 let camera, controls, scene, renderer, tiles, cameraHelper;
 let thirdPersonCamera, thirdPersonRenderer, thirdPersonControls;
 let box;
+let offsetParent;
 let statsContainer, stats;
 let params = {
   'errorTarget': 6,
   'errorThreshold': 60,
   'maxDepth': 15,
   'loadSiblings': true,
+  'up': '+Y',
   'displayBounds': false,
   'showThirdPerson': true,
   'reload': reinstantiateTiles
@@ -42349,13 +42384,14 @@ init();
 animate();
 
 function reinstantiateTiles() {
+  const url = window.location.hash.replace(/^#/, '') || './SampleTileset/tileset.json';
+
   if (tiles) {
-    scene.remove(tiles.group);
+    offsetParent.remove(tiles.group);
   }
 
-  tiles = new _ThreeTilesRenderer.ThreeTilesRenderer('https://m20-rps-asttro-terrain.s3-us-gov-west-1.amazonaws.com/sol/00000/ids/tileset/0000_0020536/0000_0020536_tileset.json', camera, renderer);
-  scene.add(tiles.group);
-  tiles.group.rotation.x = Math.PI / 2;
+  tiles = new _ThreeTilesRenderer.ThreeTilesRenderer(url, camera, renderer);
+  offsetParent.add(tiles.group);
 }
 
 function init() {
@@ -42404,6 +42440,8 @@ function init() {
   var ambLight = new _three.AmbientLight(0x222222);
   scene.add(ambLight);
   box = new _three.Box3();
+  offsetParent = new _three.Group();
+  scene.add(offsetParent);
   reinstantiateTiles();
   onWindowResize();
   window.addEventListener('resize', onWindowResize, false); // GUI
@@ -42414,6 +42452,7 @@ function init() {
   tiles.add(params, 'errorTarget').min(0).max(50);
   tiles.add(params, 'errorThreshold').min(0).max(1000);
   tiles.add(params, 'maxDepth').min(1).max(100);
+  tiles.add(params, 'up', ['+Y', '-Z']);
   tiles.open();
   gui.add(params, 'displayBounds');
   gui.add(params, 'showThirdPerson');
@@ -42471,7 +42510,13 @@ function animate() {
   tiles.displayBounds = params.displayBounds; // update tiles
 
   tiles.update();
-  window.tiles = tiles; // update tiles center
+  window.tiles = tiles;
+  offsetParent.rotation.set(0, 0, 0);
+
+  if (params.up === '-Z') {
+    offsetParent.rotation.x = Math.PI / 2;
+  } // update tiles center
+
 
   if (tiles.getBounds(box)) {
     box.getCenter(tiles.group.position);
@@ -42526,7 +42571,7 @@ var parent = module.bundle.parent;
 if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
   var hostname = "" || location.hostname;
   var protocol = location.protocol === 'https:' ? 'wss' : 'ws';
-  var ws = new WebSocket(protocol + '://' + hostname + ':' + "63925" + '/');
+  var ws = new WebSocket(protocol + '://' + hostname + ':' + "58020" + '/');
 
   ws.onmessage = function (event) {
     checkedAssets = {};

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
example/bundle/example.e31bb0bc.js.map


+ 1 - 1
example/bundle/index.js

@@ -212,7 +212,7 @@ var parent = module.bundle.parent;
 if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
   var hostname = "" || location.hostname;
   var protocol = location.protocol === 'https:' ? 'wss' : 'ws';
-  var ws = new WebSocket(protocol + '://' + hostname + ':' + "63925" + '/');
+  var ws = new WebSocket(protocol + '://' + hostname + ':' + "58020" + '/');
 
   ws.onmessage = function (event) {
     checkedAssets = {};

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
example/bundle/index.js.map


+ 19 - 5
example/index.js

@@ -1,5 +1,5 @@
 import { ThreeTilesRenderer } from '../src/ThreeTilesRenderer.js';
-import { Scene, DirectionalLight, AmbientLight, WebGLRenderer, PerspectiveCamera, CameraHelper, Box3, Raycaster, Vector2, Ray, Mesh, CylinderBufferGeometry, MeshBasicMaterial } from 'three';
+import { Scene, DirectionalLight, AmbientLight, WebGLRenderer, PerspectiveCamera, CameraHelper, Box3, Raycaster, Vector2, Ray, Mesh, CylinderBufferGeometry, MeshBasicMaterial, Group } from 'three';
 import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
 import * as dat from 'three/examples/jsm/libs/dat.gui.module.js';
 import Stats from 'three/examples/jsm/libs/stats.module.js';
@@ -8,6 +8,7 @@ let camera, controls, scene, renderer, tiles, cameraHelper;
 let thirdPersonCamera, thirdPersonRenderer, thirdPersonControls;
 let box;
 let raycaster, mouse, rayIntersect;
+let offsetParent;
 let statsContainer, stats;
 
 let params = {
@@ -17,6 +18,7 @@ let params = {
 	'maxDepth': 15,
 	'loadSiblings': true,
 
+	'up': '+Y',
 	'displayBounds': false,
 	'showThirdPerson': true,
 	'reload': reinstantiateTiles,
@@ -28,15 +30,16 @@ animate();
 
 function reinstantiateTiles() {
 
+	const url = window.location.hash.replace( /^#/, '' ) || './SampleTileset/tileset.json';
+
 	if ( tiles ) {
 
-		scene.remove( tiles.group );
+		offsetParent.remove( tiles.group );
 
 	}
 
-	tiles = new ThreeTilesRenderer( './SampleTileset/tileset.json', camera, renderer );
-	scene.add( tiles.group );
-	tiles.group.rotation.x = Math.PI / 2;
+	tiles = new ThreeTilesRenderer( url, camera, renderer );
+	offsetParent.add( tiles.group );
 
 }
 
@@ -101,6 +104,9 @@ function init() {
 	rayIntersect = new Mesh( new CylinderBufferGeometry( 0.25, 0.25, 5 ), new MeshBasicMaterial( { color: 0xff0000 } ) );
 	scene.add( rayIntersect );
 
+	offsetParent = new Group();
+	scene.add( offsetParent );
+
 	reinstantiateTiles();
 
 	onWindowResize();
@@ -114,6 +120,7 @@ function init() {
 	tiles.add( params, 'errorTarget' ).min( 0 ).max( 50 );
 	tiles.add( params, 'errorThreshold' ).min( 0 ).max( 1000 );
 	tiles.add( params, 'maxDepth' ).min( 1 ).max( 100 );
+	tiles.add( params, 'up', [ '+Y', '-Z' ] );
 	tiles.open();
 
 	gui.add( params, 'displayBounds' );
@@ -208,6 +215,13 @@ function animate() {
 	tiles.update();
 	window.tiles = tiles;
 
+	offsetParent.rotation.set( 0, 0, 0 );
+	if ( params.up === '-Z' ) {
+
+		offsetParent.rotation.x = Math.PI / 2;
+
+	}
+
 	// update tiles center
 	if ( tiles.getBounds( box ) ) {
 

+ 30 - 11
package-lock.json

@@ -4925,7 +4925,8 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -4946,12 +4947,14 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -4966,17 +4969,20 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5093,7 +5099,8 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5105,6 +5112,7 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5119,6 +5127,7 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -5126,12 +5135,14 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -5150,6 +5161,7 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -5230,7 +5242,8 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -5242,6 +5255,7 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5327,7 +5341,8 @@
         "safe-buffer": {
           "version": "5.1.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5363,6 +5378,7 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -5382,6 +5398,7 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -5425,12 +5442,14 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
           "bundled": true,
-          "dev": true
+          "dev": true,
+          "optional": true
         }
       }
     },

+ 11 - 0
package.json

@@ -2,6 +2,17 @@
   "name": "3d-tiles-renderer",
   "version": "0.0.1",
   "description": "https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification",
+  "keywords": [
+    "3d-tiles",
+    "3dtiles",
+    "graphics",
+    "b3dm",
+    "threejs",
+    "rendering",
+    "geometry",
+    "terrain",
+    "cesium"
+  ],
   "main": "src/index.js",
   "scripts": {
     "start": "parcel watch example/index.html --out-dir ./example/bundle/ --public-url . --no-cache",

+ 45 - 24
src/ThreeTilesRenderer.js

@@ -41,7 +41,7 @@ class TilesGroup extends Group {
 	raycast( raycaster, intersects ) {
 
 		// TODO: Figure out how to do traversal here -- submit issue to three.js?
-		const tilesRenderer = tilesRenderer;
+		const tilesRenderer = this.tilesRenderer;
 		const visibleSet = tilesRenderer.visibleSet;
 		const activeSet = tilesRenderer.activeSet;
 
@@ -178,18 +178,15 @@ class ThreeTilesRenderer extends TilesRenderer {
 
 		}
 
-		const d = this.root.boundingVolume.box;
-		box.min.set(
-			d[ 0 ] - d[ 3 ],
-			d[ 1 ] - d[ 7 ],
-			d[ 2 ] - d[ 11 ]
-		);
+		const cached = this.root.cached;
+		const boundingBox = cached.box;
+		const obbMat = cached.boxTransform;
+		const transformMat = cached.transform;
+
+		box.copy( boundingBox );
+		tempMat.multiplyMatrices( transformMat, obbMat );
+		box.applyMatrix4( tempMat );
 
-		box.max.set(
-			d[ 0 ] + d[ 3 ],
-			d[ 1 ] + d[ 7 ],
-			d[ 2 ] + d[ 11 ]
-		);
 
 		return true;
 
@@ -201,12 +198,9 @@ class ThreeTilesRenderer extends TilesRenderer {
 		const group = this.group;
 		this.traverse( tile => {
 
-			const cached = tile.cached;
-			const groupMatrixWorld = group.matrixWorld;
-			const transformMat = cached.transform;
+			const cached = tile.tempMat.copy( transformMat ); group.matrixWorld;
 
-			tempMat.copy( groupMatrixWorld );
-			tempMat.multiply( transformMat );
+			tempMat.copy( transformMat );;
 
 			const sphere = cached.sphere;
 			if ( sphere ) {
@@ -272,7 +266,7 @@ class ThreeTilesRenderer extends TilesRenderer {
 
 		}
 
-		// store the camera frustums
+		// store the camera frustums in the 3d tiles root frame
 		for ( let i = 0, l = frustums.length; i < l; i ++ ) {
 
 			const camera = cameras[ i ];
@@ -313,6 +307,12 @@ class ThreeTilesRenderer extends TilesRenderer {
 
 		}
 
+		if ( parentTile ) {
+
+			transform.multiply( parentTile.cached.transform );
+
+		}
+
 		let box = null;
 		let boxTransform = null;
 		if ( 'box' in tile.boundingVolume ) {
@@ -519,28 +519,27 @@ class ThreeTilesRenderer extends TilesRenderer {
 
 		const cached = tile.cached;
 		const cameras = this.cameras;
-		const group = this.group;
 
 		// TODO: Use the content bounding volume here?
 		const boundingVolume = tile.boundingVolume;
-		const groupMatrixWorld = group.matrixWorld;
 		const transformMat = cached.transform;
 
 		if ( 'box' in boundingVolume ) {
 
+			const group = this.group;
 			const boundingBox = cached.box;
 			const obbMat = cached.boxTransform;
 
 			// TODO: these can likely be cached? Or the world transform mat can be used
 			// transformMat can be rolled into oobMat
-			tempMat.copy( groupMatrixWorld );
-			tempMat.multiply( transformMat );
+			tempMat.copy( transformMat );
 			tempMat.multiply( obbMat );
 			tempMat.getInverse( tempMat );
 
 			let minError = Infinity;
 			for ( let i = 0, l = cameras.length; i < l; i ++ ) {
 
+				// transform camera position into local frame of the tile bounding box
 				const cam = cameras[ i ];
 				tempVector.copy( cam.position );
 				tempVector.applyMatrix4( tempMat );
@@ -556,8 +555,27 @@ class ThreeTilesRenderer extends TilesRenderer {
 				} else {
 
 					const distance = boundingBox.distanceToPoint( tempVector );
+
+					// assume the scales on all axes are uniform.
+					let scale;
+
+					// account for tile scale.
+					tempVector.setFromMatrixScale( tempMat );
+					scale = tempVector.x;
+
+					// account for parent group scale. Divide because this matrix has not been inverted like the previous one.
+					tempVector.setFromMatrixScale( group.matrixWorld );
+					scale /= tempVector.x;
+
+					if ( Math.abs( Math.max( scale.x - scale.y, scale.x - scale.z ) ) > 1e-6 ) {
+
+						console.warn( 'ThreeTilesRenderer : Non uniform scale used for tile which may cause issues when claculating screen space error.' );
+
+					}
+
+					const scaledDistance = distance * scale;
 					const sseDenominator = 2 * Math.tan( 0.5 * cam.fov * DEG2RAD );
-					error = ( tile.geometricError * resVector.height ) / ( distance * sseDenominator );
+					error = ( tile.geometricError * resVector.height ) / ( scaledDistance * sseDenominator );
 
 				}
 
@@ -592,11 +610,14 @@ class ThreeTilesRenderer extends TilesRenderer {
 		const sphere = tile.cached.sphere;
 		if ( sphere ) {
 
+			_sphere.copy( sphere );
+			_sphere.applyMatrix4( tile.cached.transform );
+
 			const frustums = this.frustums;
 			for ( let i = 0, l = frustums.length; i < l; i ++ ) {
 
 				const frustum = frustums[ i ];
-				if ( frustum.intersectsSphere( sphere ) ) {
+				if ( frustum.intersectsSphere( _sphere ) ) {
 
 					return true;
 

+ 22 - 4
src/TilesRenderer.js

@@ -330,7 +330,16 @@ class TilesRenderer {
 
 	get root() {
 
-		return this.tileSets[ this.rootSet ].root;
+		const tileSet = this.tileSets[ this.rootSet ];
+		if ( ! tileSet || tileSet instanceof Promise ) {
+
+			return null;
+
+		} else {
+
+			return tileSet.root;
+
+		}
 
 	}
 
@@ -368,7 +377,7 @@ class TilesRenderer {
 
 		const tileSets = this.tileSets;
 		const rootTileSet = tileSets[ this.rootSet ];
-		if ( ! rootTileSet.root ) return;
+		if ( ! rootTileSet || ! rootTileSet.root ) return;
 
 		traverseSet( rootTileSet.root, cb );
 
@@ -381,7 +390,7 @@ class TilesRenderer {
 		const lruCache = this.lruCache;
 		const tileSets = this.tileSets;
 		const rootTileSet = tileSets[ this.rootSet ];
-		if ( ! rootTileSet.root ) return;
+		if ( ! rootTileSet || ! rootTileSet.root ) return;
 
 		const root = rootTileSet.root;
 
@@ -447,6 +456,7 @@ class TilesRenderer {
 		}
 
 		tile.parent = parentTile;
+		tile.children = tile.children || [];
 		tile.__contentEmpty = ! tile.content || ! tile.content.uri;
 
 		tile.__error = 0.0;
@@ -510,7 +520,15 @@ class TilesRenderer {
                 fetch( url, { credentials: 'same-origin' } )
                 	.then( res => {
 
-                		return res.json();
+                		if ( res.ok ) {
+
+                			return res.json();
+
+                		} else {
+
+                			throw new Error( `Status ${ res.status } (${ res.statusText })` );
+
+                		}
 
                 	} )
                 	.then( json => {