Skip to content

Commit

Permalink
Merge branch 'master' of github.com:NASA-AMMOS/3DTilesRendererJS
Browse files Browse the repository at this point in the history
  • Loading branch information
gkjohnson committed Oct 22, 2024
2 parents 5b20d29 + a9d5449 commit d0932f2
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 23 deletions.
49 changes: 26 additions & 23 deletions example/r3f/globe.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { StrictMode, useRef } from 'react';
import { createRoot } from 'react-dom/client';

// TilesRenderer, controls and attribution imports
import { TilesPlugin, TilesRenderer, TilesAttributionOverlay, GlobeControls, EastNorthUpFrame } from '3d-tiles-renderer/r3f';
import {
TilesPlugin,
TilesRenderer,
TilesAttributionOverlay,
GlobeControls,
EastNorthUpFrame,
CompassGizmo,
} from '3d-tiles-renderer/r3f';

// Plugins
import { GoogleCloudAuthPlugin } from '3d-tiles-renderer';
Expand All @@ -14,7 +21,7 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';

// R3F, DREI and LEVA imports
import { Canvas, useFrame } from '@react-three/fiber';
import { Environment, GizmoHelper, GizmoViewport } from '@react-three/drei';
import { Environment } from '@react-three/drei';
import { useControls } from 'leva';
import { MathUtils, Vector3 } from 'three';

Expand Down Expand Up @@ -82,38 +89,34 @@ function App() {
3D Tiles renderer tile set
Use a "key" property to ensure the tiles renderer gets recreated when the api token or asset change
*/}
<group rotation-x={ - Math.PI / 2 }>
<TilesRenderer key={ apiToken }>
<TilesPlugin plugin={ GoogleCloudAuthPlugin } args={ { apiToken } } />
<TilesPlugin plugin={ GLTFExtensionsPlugin } dracoLoader={ dracoLoader } />
<TilesPlugin plugin={ TileCompressionPlugin } />
<TilesPlugin plugin={ UpdateOnChangePlugin } />
<TilesPlugin plugin={ TilesFadePlugin } />
<TilesRenderer key={ apiToken } group={ { rotation: [ - Math.PI / 2, 0, 0 ] } }>
<TilesPlugin plugin={ GoogleCloudAuthPlugin } args={ { apiToken } } />
<TilesPlugin plugin={ GLTFExtensionsPlugin } dracoLoader={ dracoLoader } />
<TilesPlugin plugin={ TileCompressionPlugin } />
<TilesPlugin plugin={ UpdateOnChangePlugin } />
<TilesPlugin plugin={ TilesFadePlugin } />

{/* Controls */}
<GlobeControls enableDamping={ true } />
{/* Controls */}
<GlobeControls enableDamping={ true } />

{/* Attributions */}
<TilesAttributionOverlay />
{/* Attributions */}
<TilesAttributionOverlay />

{/* Pointer to NASA JPL */}
<EastNorthUpFrame lat={ 34.2013 * MathUtils.DEG2RAD } lon={ - 118.1714 * MathUtils.DEG2RAD } height={ 350 }>
<Pointer />
</EastNorthUpFrame>;
</TilesRenderer>
</group>
{/* Pointer to NASA JPL */}
<EastNorthUpFrame lat={ 34.2013 * MathUtils.DEG2RAD } lon={ - 118.1714 * MathUtils.DEG2RAD } height={ 350 }>
<Pointer />
</EastNorthUpFrame>;

{/* Add compass gizmo */}
<CompassGizmo />
</TilesRenderer>

{/* other r3f staging */}
<Environment
preset="sunset"
backgroundBlurriness={ 0.9 }
environmentIntensity={ 1 }
/>
<GizmoHelper alignment="bottom-right">
<GizmoViewport />
</GizmoHelper>

</Canvas>
);

Expand Down
25 changes: 25 additions & 0 deletions src/r3f/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,3 +207,28 @@ The `TilesAttributionOverlay` component must be embedded in a tile set and will
<TilesAttributionOverlay />
</TilesRenderer>
```

## CompassGizmo

Adds a compass to the bottom right of the page that orients to "north" based on the camera position and orientation. Must be nested in a `TilesRenderer` component.

Any children passed into the class will replace the default red and white compass design with +Y pointing north and +X pointing east. The graphic children should fit within a volume from - 0.5 to 0.5 along all axes.

```jsx
<CompassGizmo
{/* Specifies whether the compass will render in '2d' or '3d' */}
mode={ '3d' }

{/* The size of the compass in pixels*/}
scale={ 35 }

{/* The number pixels in margin to add relative to the bottom right of the screen */}
margin={ 10 }

{/* Whether to render the main scene */}
overrideRenderLoop={ true }

{/* Any remaining props including click events are passed through to the parent group */}
onClick={ () => console.log( 'compass clicked!' ) }
/>
```
215 changes: 215 additions & 0 deletions src/r3f/components/CompassGizmo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { createPortal, useFrame, useThree } from '@react-three/fiber';
import { useContext, useEffect, useMemo, useRef } from 'react';
import { BackSide, Matrix4, OrthographicCamera, Scene, Vector3 } from 'three';
import { TilesRendererContext } from './TilesRenderer';

// Based in part on @pmndrs/drei's Gizmo component

const _vec = /*@__PURE__*/ new Vector3();
const _axis = /*@__PURE__*/ new Vector3();
const _matrix = /*@__PURE__*/ new Matrix4();
const _enuMatrix = /*@__PURE__*/ new Matrix4();
const _cart = {};

// Renders the portal with an orthographic camera
function RenderPortal( props ) {

const { defaultScene, defaultCamera, overrideRenderLoop = true } = props;
const camera = useMemo( () => new OrthographicCamera(), [] );
const [ set, size, gl, scene ] = useThree( state => [ state.set, state.size, state.gl, state.scene ] );
useEffect( () => {

set( { camera } );

}, [ set, camera ] );

useEffect( () => {

camera.left = - size.width / 2;
camera.right = size.width / 2;
camera.top = size.height / 2;
camera.bottom = - size.height / 2;
camera.near = 0;
camera.far = 2000;
camera.position.z = camera.far / 2;
camera.updateProjectionMatrix();

}, [ camera, size ] );

useFrame( () => {

if ( overrideRenderLoop ) {

gl.render( defaultScene, defaultCamera );

}

const currentAutoClear = gl.autoClear;
gl.autoClear = false;

gl.clearDepth();
gl.render( scene, camera );

gl.autoClear = currentAutoClear;

}, 1 );

}

// generates an extruded box geometry
function TriangleGeometry() {

const ref = useRef();
useEffect( () => {

const geometry = ref.current;
const position = geometry.attributes.position;
for ( let i = 0, l = position.count; i < l; i ++ ) {

_vec.fromBufferAttribute( position, i );
if ( _vec.y > 0 ) {

_vec.x = 0;
position.setXYZ( i, ..._vec );

}

}

} );

return <boxGeometry ref={ ref } />;

}

// renders a typical compass graphic with red north triangle, white south, and a tinted circular background
function CompassGraphic( { northColor = 0xEF5350, southColor = 0xFFFFFF } ) {

const groupRef = useRef();

return (
<group scale={ 0.5 } ref={ groupRef }>

{/* Lights */}
<ambientLight intensity={ 1.5 }/>
<directionalLight position={ [ 0, 2, 3 ] } target={ groupRef.current } />
<directionalLight position={ [ 0, - 2, - 3 ] } target={ groupRef.current } />

{/* Background */}
<mesh>
<sphereGeometry />
<meshBasicMaterial color={ 0 } opacity={ 0.3 } transparent={ true } side={ BackSide } />
</mesh>

{/* Compass shape */}
<group scale={ [ 0.5, 1, 0.15 ] }>
<mesh position-y={ 0.5 }>
<TriangleGeometry />
<meshStandardMaterial color={ northColor } />
</mesh>
<mesh position-y={ - 0.5 } rotation-x={ Math.PI }>
<TriangleGeometry />
<meshStandardMaterial color={ southColor } />
</mesh>
</group>
</group>
);

}

export function CompassGizmo( { children, overrideRenderLoop, mode = '3d', margin = 10, scale = 35, ...rest } ) {

const [ defaultCamera, defaultScene, size ] = useThree( state => [ state.camera, state.scene, state.size ] );
const tiles = useContext( TilesRendererContext );
const groupRef = useRef( null );
const scene = useMemo( () => {

return new Scene();

}, [] );

useFrame( () => {

if ( tiles === null || groupRef.current === null ) {

return;

}

const { ellipsoid } = tiles;
const group = groupRef.current;

// get the ENU frame in world space
_matrix.copy( tiles.group.matrixWorld ).invert();
_vec.setFromMatrixPosition( defaultCamera.matrixWorld ).applyMatrix4( _matrix );
ellipsoid.getPositionToCartographic( _vec, _cart );
ellipsoid
.getRotationMatrixFromAzElRoll( _cart.lat, _cart.lon, 0, 0, 0, _enuMatrix )
.premultiply( tiles.group.matrixWorld );

// get the camera orientation in the local ENU frame
_enuMatrix.invert();
_matrix.copy( defaultCamera.matrixWorld ).premultiply( _enuMatrix );

if ( mode.toLowerCase() === '3d' ) {

group.quaternion.setFromRotationMatrix( _matrix ).invert();

} else {

// get the projected facing direction of the camera
_vec.set( 0, 1, 0 ).transformDirection( _matrix ).normalize();
_vec.z = 0;
_vec.normalize();

if ( _vec.length() === 0 ) {

// if we're looking exactly top-down
group.quaternion.identity();

} else {

// compute the 2d looking direction
const angle = _axis.set( 0, 1, 0 ).angleTo( _vec );
_axis.cross( _vec ).normalize();
group.quaternion.setFromAxisAngle( _axis, - angle );

}

}

} );

// default to the compass graphic
if ( ! children ) {

children = <CompassGraphic />;

}

return (
createPortal(
<>
<group
ref={ groupRef }
scale={ scale }
position={ [
size.width / 2 - margin - scale / 2,
- size.height / 2 + margin + scale / 2,
0,
] }

{ ...rest }
>{ children }</group>
<RenderPortal
defaultCamera={ defaultCamera }
defaultScene={ defaultScene }
overrideRenderLoop={ overrideRenderLoop }
/>
</>,
scene,
{ events: { priority: 1 } },
)
);

}
1 change: 1 addition & 0 deletions src/r3f/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './components/TilesRenderer.jsx';
export * from './components/TilesAttributionOverlay.jsx';
export * from './components/CanvasDOMOverlay.jsx';
export * from './components/CameraControls.jsx';
export * from './components/CompassGizmo.jsx';

0 comments on commit d0932f2

Please sign in to comment.