Skip to content

Commit

Permalink
Add new plugins for reorienation, gltf load customization (#785)
Browse files Browse the repository at this point in the history
* Add plugin for gltf extensions

* Add reorientation plugin

* Fix reorientation plugin

* Fix both plugins

* Add docs

* Fix reorientation plugin
  • Loading branch information
gkjohnson authored Oct 5, 2024
1 parent 0fc2ef9 commit b4a78c0
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 0 deletions.
70 changes: 70 additions & 0 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,76 @@ fadeRootTiles = false : boolean

Whether to fade the root tile objects in.

## GLTFExtensionsPlugin

Plugin for automatically adding common extensions and loaders for 3d tiles to the GLTFLoader used for parsing tile geometry.

### .constructor

```js
constructor( options : Object )
```

Available options are as follows:

```js
{
// If true then the StructuralMetadata and MeshFeatures extensions are included.
metadata: true,

// If true then the Cesium RTC Center extension is included.
rtc: true,

// A list of other extensions to include in the loader. All elements are passed to the "GLTFLoader.register" function.
plugins: [],

// DRACOLoader and KTX2Loader instances to add to the loader.
dracoLoader: null,
ktxLoader: null,

// Whether to automatically dispose of the DRACO and KTX Loaders when the plugin is disposed.
autoDispose: true,
}
```

## ReorientationPlugin

Plugin for automatically re-orienting and re-centering the tile set to make it visible near the origin and facing the right direction.

### .constructor

```js
constructor( options : Object )
```

Available options are as follows:

```js
{
// The latitude, longitude, and height of the point on the surface to orient to. Lat and lon are in radians. If
// no coordinates are provided then the plugin tries to determine if the tile set is a tile set on the globe surface
// and estimates the coordinates.
lat: null,
lon: null,
height: 0,

// If a set of lat and lon coordinates cannot be determined then the tile set is simple oriented so the provided axes
// is oriented to three.js' +Y up direction. Valid values are positive or negative x, y, or z.
up: '+z',

// Whether or not to recenter the tile set.
recenter: true,
}
```

### transformLatLonHeightToOrigin

```js
transformLatLonHeightToOrigin( lat, lon, height = 0 ) : void
```

Transforms the centers the tile set such that the given coordinates and height are positioned at the origin with "X" facing west and "Z" facing north.

# Controls

## EnvironmentControls
Expand Down
81 changes: 81 additions & 0 deletions example/src/plugins/GLTFExtensionsPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { GLTFStructuralMetadataExtension, GLTFMeshFeaturesExtension, GLTFCesiumRTCExtension } from '../../../src/index.js';

export class GLTFExtensionsPlugin {

constructor( options ) {

options = {
metadata: true,
rtc: true,

plugins: [],

dracoLoader: null,
ktxLoader: null,
autoDispose: true,
...options,
};

this.tiles = null;

this.metadata = options.metadata;
this.rtc = options.rtcPlugin;
this.plugins = options.plugins;

this.dracoLoader = options.dracoLoader;
this.ktxLoader = options.ktxLoader;
this._regex = /\.(gltf|glb)$/g;
this._loader = null;

}

init( tiles ) {

const loader = new GLTFLoader( tiles.manager );
if ( this.dracoLoader ) {

loader.setDRACOLoader( this.dracoLoader );

}

if ( this.ktxLoader ) {

loader.setKTX2Loader( this.ktxLoader );

}

if ( this.rtc ) {

loader.register( () => new GLTFCesiumRTCExtension() );

}

if ( this.metadata ) {

loader.register( () => new GLTFStructuralMetadataExtension() );
loader.register( () => new GLTFMeshFeaturesExtension() );

}

this.plugins.forEach( plugin => loader.register( plugin ) );

tiles.manager.addHandler( this._regex, loader );
this.tiles = tiles;
this._loader = loader;

}

dispose() {

this.tiles.manager.removeHandler( this._regex );
if ( this.autoDispose ) {

this.ktxLoader.dispose();
this.dracoLoader.dispose();

}

}

}
136 changes: 136 additions & 0 deletions example/src/plugins/ReorientationPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Sphere, Vector3 } from 'three';
import { OBJECT_FRAME } from '../../../src/index.js';

const sphere = /* @__PURE__ */ new Sphere();
const vec = /* @__PURE__ */ new Vector3();
export class ReorientationPlugin {

constructor( options ) {

options = {
up: '+z',
recenter: true,

lat: null,
lon: null,
height: 0,
...options,
};

this.tiles = null;

this.up = options.up.toLowerCase().replace( /\s+/, '' );
this.lat = options.lat;
this.lon = options.lon;
this.height = options.height;
this.recenter = options.recenter;
this._callback = null;

}

init( tiles ) {

this.tiles = tiles;

this._callback = () => {

const { up, lat, lon, height, recenter } = this;

if ( lat !== null && lon !== null ) {

// if the latitude and longitude are provided then remove the position offset
this.transformLatLonHeightToOrigin( lat, lon, height );

} else {

const { ellipsoid } = tiles;
const minRadii = Math.min( ...ellipsoid.radius );
tiles.getBoundingSphere( sphere );
if ( sphere.center.length() > minRadii * 0.5 ) {

// otherwise see if this is possibly a tile set on the surface of the globe based on the positioning
const cart = {};
ellipsoid.getPositionToCartographic( sphere.center, cart );
this.transformLatLonHeightToOrigin( cart.lat, cart.lon, cart.height );

} else {

// lastly fall back to orienting the up direction to +Y
const group = tiles.group;
group.rotation.set( 0, 0, 0 );
switch ( up ) {

case 'x': case '+x':
group.rotation.z = Math.PI / 2;
break;
case '-x':
group.rotation.z = - Math.PI / 2;
break;

case 'y': case '+y':
break;
case '-y':
group.rotation.z = Math.PI;
break;

case 'z': case '+z':
group.rotation.x = - Math.PI / 2;
break;
case '-z':
group.rotation.x = Math.PI / 2;
break;

}

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

}

}

if ( ! recenter ) {

tiles.group.position.setScalar( 0 );

}

tiles.removeEventListener( 'load-tile-set', this._callback );

};

tiles.addEventListener( 'load-tile-set', this._callback );

}

transformLatLonHeightToOrigin( lat, lon, height = 0 ) {

const { group, ellipsoid } = this.tiles;

// get ENU orientation (Z facing north and X facing west) and position
ellipsoid.getRotationMatrixFromAzElRoll( lat, lon, 0, 0, 0, group.matrix, OBJECT_FRAME );
ellipsoid.getCartographicToPosition( lat, lon, height, vec );

// adjust the group matrix
group.matrix
.setPosition( vec )
.invert()
.decompose( group.position, group.quaternion, group.scale );
group.updateMatrixWorld();

}

dispose() {

const { group } = this.tiles;
group.position.setScalar( 0 );
group.quaternion.identity();
group.scale.set( 1, 1, 1 );

this.tiles.addEventListener( 'load-tile-set', this._callback );

}

}
2 changes: 2 additions & 0 deletions src/three/TilesRenderer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { Box3, Camera, Vector2, Matrix4, WebGLRenderer, Object3D, LoadingManager
import { Tile } from '../base/Tile';
import { TilesRendererBase } from '../base/TilesRendererBase';
import { TilesGroup } from './TilesGroup';
import { Ellipsoid } from './math/Ellipsoid';

export class TilesRenderer extends TilesRendererBase {

ellipsoid: Ellipsoid;
autoDisableRendererCulling : Boolean;
optimizeRaycast : Boolean;

Expand Down

0 comments on commit b4a78c0

Please sign in to comment.