Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ See the [Three.js usage guide](./USAGE.md) or [Babylon.js usage guide](./src/bab
| [Quantized Mesh](https://nasa-ammos.github.io/3DTilesRendererJS/three/quantMeshOverlays.html) | Quantized mesh with overlays |
| [Load Region](https://nasa-ammos.github.io/3DTilesRendererJS/three/loadRegion.html) | Loading tiles in region volumes |
| [GeoJSON](https://nasa-ammos.github.io/3DTilesRendererJS/three/geojson.html) | GeoJSON overlays |
| [3TZ Archive](https://nasa-ammos.github.io/3DTilesRendererJS/three/tz3Example.html) | Streaming a `.3tz` single-file tileset archive via HTTP range requests |

¹ Requires a [Google Tiles API Key](https://developers.google.com/maps/documentation/tile/3d-tiles) or [Cesium Ion API Key](https://cesium.com/platform/cesium-ion/)

Expand Down
12 changes: 12 additions & 0 deletions example/three/tz3Example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!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 — .3tz Archive Example</title>
<link rel="stylesheet" href="../styles.css">
</head>
<body>
<script src="./tz3Example.js" type="module"></script>
</body>
</html>
124 changes: 124 additions & 0 deletions example/three/tz3Example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
TilesRenderer,
} from '3d-tiles-renderer';
import {
TZ3Plugin,
ImplicitTilingPlugin,
} from '3d-tiles-renderer/plugins';
import {
Scene,
DirectionalLight,
AmbientLight,
WebGLRenderer,
PerspectiveCamera,
Box3,
Sphere,
Group,
} from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let camera, controls, scene, renderer, tiles, offsetParent;

const params = {
// Replace with the URL of a publicly hosted .3tz file.
url: new URLSearchParams( window.location.search ).get( 'url' ) || 'https://s3.us-east-2.wasabisys.com/testing/20260428-3tz-sampledata.3tz',
};

init();
animate();

function reinstantiateTiles() {

if ( tiles ) {

offsetParent.remove( tiles.group );
tiles.dispose();
tiles = null;

}

tiles = new TilesRenderer( params.url );
tiles.registerPlugin( new TZ3Plugin() );
tiles.registerPlugin( new ImplicitTilingPlugin() );

tiles.addEventListener( 'load-tileset', () => {

const box = new Box3();
const sphere = new Sphere();
if ( tiles.getBoundingBox( box ) ) {

box.getCenter( tiles.group.position ).multiplyScalar( - 1 );

} else if ( tiles.getBoundingSphere( sphere ) ) {

tiles.group.position.copy( sphere.center ).multiplyScalar( - 1 );

}

} );

tiles.setCamera( camera );
tiles.setResolutionFromRenderer( camera, renderer );
offsetParent.add( tiles.group );

}

function init() {

scene = new Scene();

renderer = new WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor( 0x151c1f );
document.body.appendChild( renderer.domElement );

camera = new PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 4000 );
camera.position.set( 400, 400, 400 );

controls = new OrbitControls( camera, renderer.domElement );
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 2000;

const dirLight = new DirectionalLight( 0xffffff, 1.25 );
dirLight.position.set( 1, 2, 3 ).multiplyScalar( 40 );
scene.add( dirLight );
scene.add( new AmbientLight( 0xffffff, 0.2 ) );

offsetParent = new Group();
scene.add( offsetParent );

reinstantiateTiles();

window.addEventListener( 'resize', onWindowResize, false );
onWindowResize();

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

requestAnimationFrame( animate );

controls.update();
camera.updateMatrixWorld();

if ( tiles ) {

tiles.setResolutionFromRenderer( camera, renderer );
tiles.update();

}

renderer.render( scene, camera );

}
20 changes: 20 additions & 0 deletions src/core/plugins/TZ3Plugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface TZ3PluginOptions {

/**
* Extra fetch options (headers, credentials, signal, etc.) forwarded to
* every range request made against the archive.
*/
fetchOptions?: RequestInit;

}

/**
* Plugin that adds support for the ".3tz" single-file 3D Tiles archive
* format. Intercepts fetches for URLs pointing at a ".3tz" file or any path
* inside one and serves the bytes via HTTP range requests.
*/
export class TZ3Plugin {

constructor( options?: TZ3PluginOptions );

}
97 changes: 97 additions & 0 deletions src/core/plugins/TZ3Plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { ZipArchiveReader } from './loaders/ZipArchiveReader.js';

// Matches a URL that points to a file path inside a .3tz archive, e.g.
// "https://example.com/foo.3tz/content/0/0.glb". Group 1 is the archive URL
// (including the ".3tz" extension), group 2 is the path inside the archive.
const ARCHIVE_PATH_RE = /^(.+?\.3tz)\/([^?#]+)(\?[^#]*)?(#.*)?$/i;

// Matches a URL whose pathname ends in ".3tz" (possibly with a query or hash).
const ARCHIVE_ROOT_RE = /^(.+?\.3tz)(\?[^#]*)?(#.*)?$/i;

/**
* Plugin that teaches the renderer how to load 3D Tiles packaged as a single
* ".3tz" archive. The plugin intercepts fetches for URLs that point at a
* ".3tz" file (or any path inside one) and serves the bytes from the archive
* via HTTP range requests, so callers do not need to unpack the archive
* server-side.
*
* The 3tz spec is a ZIP archive with a root-level "tileset.json"; see
* https://github.com/erikdahlstrom/3tz-specification.
*/
export class TZ3Plugin {

constructor( options = {} ) {

this.name = 'TZ3_PLUGIN';
this.priority = - 100;
this.tiles = null;
this.fetchOptions = options.fetchOptions || null;
this._archives = new Map();

}

init( tiles ) {

this.tiles = tiles;

}

preprocessURL( url, tile ) {

// Only rewrite the root tileset URL — inner references already carry
// a path inside the archive because base-path derivation uses the
// rewritten URL as a prefix.
if ( tile !== null ) return url;

const str = String( url );
const match = str.match( ARCHIVE_ROOT_RE );
if ( ! match ) return url;

// If the URL already points into the archive, leave it alone.
if ( ARCHIVE_PATH_RE.test( str ) ) return url;

const [ , archive, query = '', hash = '' ] = match;
return `${ archive }/tileset.json${ query }${ hash }`;

}

fetchData( url, options ) {

const str = String( url );
const match = str.match( ARCHIVE_PATH_RE );
if ( ! match ) return null;

const [ , archiveUrl, relativePath ] = match;
return this._fetchFromArchive( archiveUrl, relativePath, options );

}

async _fetchFromArchive( archiveUrl, relativePath, options ) {

let reader = this._archives.get( archiveUrl );
if ( ! reader ) {

const fetchFn = ( url, init ) => fetch( url, init );
reader = new ZipArchiveReader( archiveUrl, fetchFn, this.fetchOptions || {} );
this._archives.set( archiveUrl, reader );

}

const bytes = await reader.getFile( relativePath, options );

// Hand back a real Response so the caller can treat this path the same
// as a plain HTTP fetch (json(), arrayBuffer(), etc.).
return new Response( bytes, {
status: 200,
headers: { 'Content-Length': String( bytes.byteLength ) },
} );

}

dispose() {

this._archives.clear();

}

}
1 change: 1 addition & 0 deletions src/core/plugins/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './CesiumIonAuthPlugin.js';
export * from './GoogleCloudAuthPlugin.js';
export * from './ImplicitTilingPlugin.js';
export * from './EnforceNonZeroErrorPlugin.js';
export * from './TZ3Plugin.js';
1 change: 1 addition & 0 deletions src/core/plugins/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './CesiumIonAuthPlugin.js';
export * from './GoogleCloudAuthPlugin.js';
export * from './ImplicitTilingPlugin.js';
export * from './EnforceNonZeroErrorPlugin.js';
export * from './TZ3Plugin.js';
export * from './auth/GoogleCloudAuth.js';
export * from './auth/CesiumIonAuth.js';
export * from './loaders/QuantizedMeshLoaderBase.js';
Loading