text: import {
  	INITIAL_FRUSTUM_CULLED,
  	TilesRenderer,
  	updateFrustumCulled,
  } from "./TilesRenderer.js";
  
  import { LAZLoader } from "./LAZLoader.js";
  import { PointsMaterial } from "three";
  import path from "path-browserify";
  import { traverseSet } from "../base/traverseFunctions.js";
  import { urlJoin } from "../utilities/urlJoin.js";
  
  const splitVolumeKey = ( key ) => key.split( "-" ).map( Number );
  
  function getSubvolumes( parent ) {
  
  	const [ vd, vx, vy, vz ] = splitVolumeKey( parent );
  
  	const sd = vd + 1;
  	const sx = vx * 2;
  	const sy = vy * 2;
  	const sz = vz * 2;
  
  	return [
  		[ sd, sx, sy, sz ],
  		[ sd, sx + 1, sy, sz ],
  		[ sd, sx, sy + 1, sz ],
  		[ sd, sx + 1, sy + 1, sz ],
  		[ sd, sx, sy, sz + 1 ],
  		[ sd, sx + 1, sy, sz + 1 ],
  		[ sd, sx, sy + 1, sz + 1 ],
  		[ sd, sx + 1, sy + 1, sz + 1 ],
  	].map( ( [ d, x, y, z ] ) => `${d}-${x}-${y}-${z}` );
  
  }
  
  export class EPTRenderer extends TilesRenderer {
  
  	constructor( coreUrl, pointSize ) {
  
  		const basePath = path.dirname( coreUrl );
  		const rootUrl = urlJoin( basePath, "ept-hierarchy/0-0-0-0.json" );
  
  		super( rootUrl );
  
  		this.basePath = basePath;
  		this.coreURL = coreUrl;
  		this.material = new PointsMaterial( { size: pointSize, vertexColors: true } );
  
  	}
  
  	center() {
  
  		this.autoCenter = true;
  		if ( this.core ) this.centerGroup();
  
  	}
  
  	centerGroup() {
  
  		const [ xmin, ymin, zmin, xmax, ymax, zmax ] = this.core.bounds;
  		const mx = ( xmin + xmax ) / 2;
  		const my = ( ymin + ymax ) / 2;
  		const mz = ( zmin + zmax ) / 2;
  
  		this.group.position.set( - mx, - my, - mz );
  
  		Promise.resolve().then( () => this.update() );
  
  	}
  
  	getBoundingBox( key ) {
  
  		const [ vd, vx, vy, vz ] = splitVolumeKey( key );
  		const zoom = Math.pow( 2, vd );
  		const [ xmin, ymin, zmin, xmax, ymax, zmax ] = this.core.bounds;
  		const xlen = ( xmax - xmin ) / zoom;
  		const ylen = ( ymax - ymin ) / zoom;
  		const zlen = ( zmax - zmin ) / zoom;
  		const sx = xmin + xlen * vx;
  		const sy = ymin + ylen * vy;
  		const sz = zmin + zlen * vz;
  		const mx = sx + xlen / 2;
  		const my = sy + ylen / 2;
  		const mz = sz + zlen / 2;
  
  		return [ mx, my, mz, xlen / 2, 0, 0, 0, ylen / 2, 0, 0, 0, zlen / 2 ];
  
  	}
  
  	convertToTile( data, key ) {
  
  		const box = this.getBoundingBox( key );
  		const uri = `ept-data/${key}.laz`;
  
  		let children = getSubvolumes( key )
  			.filter( ( child ) => data[ child ] )
  			.map( ( child ) => this.convertToTile( data, child ) );
  		if ( children.length === 0 ) {
  
  			children = undefined;
  
  		}
  
  		return {
  			key,
  			boundingVolume: { box },
  			refine: "ADD",
  			content: { uri },
  			children,
  		};
  
  	}
  
  	loadHierarchy( url ) {
  
  		const hierarchies = this.hierarchies;
  		if ( ! ( url in hierarchies ) ) {
  
  			const pr = this.fetchHierarchy(
  				this.preprocessURL ? this.preprocessURL( url ) : url,
  				this.fetchOptions
  			).then( ( json ) => ( hierarchies[ url ] = json ) );
  
  			pr.catch( ( err ) => {
  
  				console.error( err );
  				hierarchies[ url ] = err;
  
  			} );
  
  			hierarchies[ url ] = pr;
  			return pr;
  
  		} else if ( hierarchies[ url ] instanceof Error ) {
  
  			return Promise.reject( hierarchies[ url ] );
  
  		} else {
  
  			return Promise.resolve( hierarchies[ url ] );
  
  		}
  
  	}
  
  	fetchTileSet( url, fetchOptions, parent = null ) {
  
  		return fetch( url, fetchOptions )
  			.then( ( res ) => {
  
  				if ( res.ok ) {
  
  					return res.json();
  
  				} else {
  
  					return new Error(
  						`EPTRenderer: Failed to load hierarchy "${url}" with status ${res.status} : ${res.statusText}`
  					);
  
  				}
  
  			} )
  			.then( ( json ) => {
  
  				if ( this.onLoadTileSet ) {
  
  					// Push this onto the end of the event stack to ensure this runs
  					// after the base renderer has placed the provided json where it
  					// needs to be placed and is ready for an update.
  					Promise.resolve().then( () => {
  
  						this.onLoadTileSet( json, url );
  
  					} );
  
  				}
  
  				const root = this.convertToTile( json, "0-0-0-0" );
  				traverseSet(
  					root,
  					( node, parent ) => this.preprocessNode( node, parent, this.basePath ),
  					null,
  					parent,
  					parent ? parent.__depth : 0
  				);
  
  				return { root };
  
  			} );
  
  	}
  
  	fetchCore( url, fetchOptions, parent = null ) {
  
  		return fetch( url, fetchOptions )
  			.then( ( res ) => {
  
  				if ( res.ok ) {
  
  					return res.json();
  
  				} else {
  
  					return new Error(
  						`EPTRenderer: Failed to load core "${url}" with status ${res.status} : ${res.statusText}`
  					);
  
  				}
  
  			} )
  			.then( ( json ) => {
  
  				console.assert(
  					json.version === "1.1.0",
  					'version is expected to be "1.1.0"'
  				);
  				console.assert(
  					json.dataType === "laszip",
  					'dataType is expected to be "laszip"'
  				);
  				console.assert(
  					json.hierarchyType === "json",
  					'hierarchyType is expected to be "json"'
  				);
  
  				return json;
  
  			} );
  
  	}
  
  	loadCore( url ) {
  
  		if ( ! this.core ) {
  
  			const pr = this.fetchCore(
  				this.preprocessURL ? this.preprocessURL( url ) : url,
  				this.fetchOptions
  			).then( ( json ) => {
  
  				this.core = json;
  				if ( this.autoCenter ) this.centerGroup();
  
  				if ( this.onLoadCore ) {
  
  					Promise.resolve().then( () => this.onLoadCore( json ) );
  
  				}
  
  			} );
  
  			pr.catch( ( err ) => {
  
  				console.error( err );
  				this.core = err;
  
  			} );
  
  			this.core = pr;
  			return pr;
  
  		} else if ( this.core instanceof Error ) {
  
  			return Promise.reject( this.core );
  
  		} else {
  
  			return Promise.resolve( this.core );
  
  		}
  
  	}
  
  	update() {
  
  		if ( ! this.core ) {
  
  			this.loadCore( this.coreURL );
  			return;
  
  		}
  
  		super.update();
  
  	}
  
  	parseTile( buffer, tile, extension ) {
  
  		tile._loadIndex = tile._loadIndex || 0;
  		tile._loadIndex ++;
  
  		const uri = tile.content.uri;
  		const uriSplits = uri.split( /[\\\/]/g );
  		uriSplits.pop();
  		const workingPath = uriSplits.join( "/" );
  		const fetchOptions = this.fetchOptions;
  
  		const manager = this.manager;
  		const loadIndex = tile._loadIndex;
  		let promise = null;
  
  		if ( extension !== "laz" ) {
  
  			console.warn( `EPTRenderer: only laz supported.` );
  			return null;
  
  		}
  
  		const loader = new LAZLoader( manager, this.material );
  		loader.workingPath = workingPath;
  		loader.fetchOptions = fetchOptions;
  		promise = loader.parse( buffer ).then( ( res ) => res.scene );
  
  		return promise.then( ( scene ) => {
  
  			if ( tile._loadIndex !== loadIndex ) {
  
  				return;
  
  			}
  
  			const cached = tile.cached;
  			const cachedTransform = cached.transform;
  
  			scene.updateMatrix();
  			scene.matrix.premultiply( cachedTransform );
  			scene.matrix.decompose( scene.position, scene.quaternion, scene.scale );
  			scene.traverse( ( c ) => {
  
  				c[ INITIAL_FRUSTUM_CULLED ] = c.frustumCulled;
  
  			} );
  			updateFrustumCulled( scene, ! this.autoDisableRendererCulling );
  
  			cached.scene = scene;
  			scene.traverse( ( c ) => {
  
  				c.raycast = this._overridenRaycast;
  
  			} );
  
  			const materials = [];
  			const geometry = [];
  			const textures = [];
  			scene.traverse( ( c ) => {
  
  				if ( c.geometry ) {
  
  					geometry.push( c.geometry );
  
  				}
  
  				if ( c.material ) {
  
  					const material = c.material;
  					materials.push( c.material );
  
  					for ( const key in material ) {
  
  						const value = material[ key ];
  						if ( value && value.isTexture ) {
  
  							textures.push( value );
  
  						}
  
  					}
  
  				}
  
  			} );
  
  			cached.materials = materials;
  			cached.geometry = geometry;
  			cached.textures = textures;
  
  			if ( this.onLoadModel ) {
  
  				this.onLoadModel( scene, tile );
  
  			}
  
  		} );
  
  	}
  
  }
  