Skip to content

Commit acb22b5

Browse files
authored
Examples: Add helper for reorientation of camera (#944)
* Add helper for reorientation of camera * Remove previous example * Add easing function, update example * docs update * Update CameraViewTransition * Updates to formatting
1 parent fa727ea commit acb22b5

File tree

6 files changed

+141
-28
lines changed

6 files changed

+141
-28
lines changed

PLUGINS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,14 @@ autoSync = true : boolean
212212
213213
Whether to automatically call the "syncCameras" function when so cameras are implicitly positioned correctly for transitioning. Disable this if syncing will happen manually and small adjustments can be made.
214214
215+
### .easeFunction
216+
217+
```js
218+
easeFunction = x => x : ( alpha: number ) => number
219+
```
220+
221+
Function for defining the transition curve between cameras.
222+
215223
### .constructor
216224
217225
```js

example/googleMapsExample.js

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ let statsContainer, stats;
3232
const params = {
3333

3434
orthographic: false,
35-
topDownOrtho: false,
3635

3736
enableCacheDisplay: false,
3837
enableRendererStats: false,
@@ -143,22 +142,6 @@ function init() {
143142

144143
// sync the camera positions and then adjust the camera views
145144
transition.syncCameras();
146-
147-
// If transitioning to ortho view then use a top-down perspective
148-
if ( v && params.topDownOrtho ) {
149-
150-
const invMat = tiles.group.matrixWorld.clone().invert();
151-
const p = transition.fixedPoint.clone().applyMatrix4( invMat );
152-
153-
const { lat, lon } = tiles.ellipsoid.getPositionToCartographic( p, {} );
154-
const { orthographicCamera } = transition;
155-
tiles.ellipsoid.getFrame( lat, lon, 0, 0, 0, 1000, orthographicCamera.matrixWorld );
156-
orthographicCamera.matrixWorld.premultiply( tiles.group.matrixWorld );
157-
orthographicCamera.matrixWorld.decompose( orthographicCamera.position, orthographicCamera.quaternion, orthographicCamera.scale );
158-
orthographicCamera.updateMatrixWorld();
159-
160-
}
161-
162145
controls.adjustCamera( transition.perspectiveCamera );
163146
controls.adjustCamera( transition.orthographicCamera );
164147

@@ -167,8 +150,6 @@ function init() {
167150
transition.toggle();
168151

169152
} );
170-
gui.add( params, 'topDownOrtho' );
171-
172153

173154
const mapsOptions = gui.addFolder( 'Google Photorealistic Tiles' );
174155
mapsOptions.add( params, 'useBatchedMesh' ).listen();
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { forwardRef, useCallback, useContext } from 'react';
2+
import { CameraTransition, TilesRendererContext } from '3d-tiles-renderer/r3f';
3+
import { useThree } from '@react-three/fiber';
4+
import { Matrix4, Quaternion, Raycaster, Vector3, MathUtils } from 'three';
5+
import { makeRotateAroundPoint } from './makeRotateAroundPoint.js';
6+
7+
const raycaster = /* @__PURE__ */ new Raycaster();
8+
const downVector = /* @__PURE__ */ new Vector3();
9+
const axis = /* @__PURE__ */ new Vector3();
10+
const quat = /* @__PURE__ */ new Quaternion();
11+
const matrix = /* @__PURE__ */ new Matrix4();
12+
13+
export const CameraViewTransition = forwardRef( function CameraViewTransition( props, ref ) {
14+
15+
const tiles = useContext( TilesRendererContext );
16+
const controls = useThree( ( { controls } ) => controls );
17+
const scene = useThree( ( { scene } ) => scene );
18+
19+
const onBeforeToggleCallback = useCallback( ( manager, targetCamera ) => {
20+
21+
if ( ! manager.animating ) {
22+
23+
targetCamera.updateMatrixWorld();
24+
25+
// Force the fixed point to be in the camera center
26+
raycaster.ray.direction.set( 0, 0, - 1 ).transformDirection( manager.camera.matrixWorld );
27+
raycaster.ray.origin.setFromMatrixPosition( manager.camera.matrixWorld );
28+
29+
const hit = raycaster.intersectObject( scene )[ 0 ];
30+
if ( hit ) {
31+
32+
manager.fixedPoint.copy( hit.point );
33+
manager.syncCameras();
34+
35+
} else {
36+
37+
controls.getPivotPoint( manager.fixedPoint );
38+
manager.syncCameras();
39+
40+
}
41+
42+
// get the normal at the target point
43+
if ( tiles ) {
44+
45+
matrix.copy( tiles.group.matrixWorld ).invert();
46+
downVector.copy( manager.fixedPoint ).applyMatrix4( matrix );
47+
tiles.ellipsoid.getPositionToNormal( downVector, downVector ).multiplyScalar( - 1 ).transformDirection( tiles.group.matrixWorld );
48+
49+
} else {
50+
51+
downVector.set( 0, - 1, 0 );
52+
53+
}
54+
55+
if ( targetCamera.isOrthographicCamera ) {
56+
57+
// transition the camera view to the top down while retaining same general pointing direction
58+
const angle = downVector.angleTo( raycaster.ray.direction );
59+
axis.crossVectors( downVector, raycaster.ray.direction );
60+
quat.setFromAxisAngle( axis, - angle ).normalize();
61+
62+
makeRotateAroundPoint( manager.fixedPoint, quat, matrix );
63+
targetCamera.matrixWorld.premultiply( matrix );
64+
targetCamera.matrixWorld.decompose(
65+
targetCamera.position,
66+
targetCamera.quaternion,
67+
targetCamera.scale,
68+
);
69+
70+
// TODO: it's possible if the fixed point isn't in the middle of the screen that
71+
// the "down" vector isn't well aligned with the ellipsoid normal. Trying to
72+
// find the middle point again after rotation and then adjusting the camera again
73+
// could help this?
74+
75+
} else {
76+
77+
// TODO: expose _isNearControls in a better way
78+
// don't tilt the camera if we're outside the "near controls" behavior so we
79+
// don't rotate the camera while out in space
80+
if ( ! controls.isGlobeControls || controls._isNearControls() ) {
81+
82+
// tilt the perspective down slightly
83+
let angle = downVector.angleTo( raycaster.ray.direction );
84+
angle = Math.max( 65 * MathUtils.DEG2RAD - angle, 0 );
85+
86+
axis.set( 1, 0, 0 ).transformDirection( targetCamera.matrixWorld );
87+
quat.setFromAxisAngle( axis, angle ).normalize();
88+
89+
makeRotateAroundPoint( manager.fixedPoint, quat, matrix );
90+
targetCamera.matrixWorld.premultiply( matrix );
91+
targetCamera.matrixWorld.decompose(
92+
targetCamera.position,
93+
targetCamera.quaternion,
94+
targetCamera.scale,
95+
);
96+
97+
}
98+
99+
}
100+
101+
}
102+
103+
controls.adjustCamera( manager.perspectiveCamera );
104+
controls.adjustCamera( manager.orthographicCamera );
105+
106+
}, [ tiles, controls, scene ] );
107+
108+
return <CameraTransition { ...props } ref={ ref } onBeforeToggle={ onBeforeToggleCallback } />;
109+
110+
} );
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Matrix4 } from 'three';
2+
3+
// helper function for constructing a matrix for rotating around a point
4+
const _matrix = new Matrix4();
5+
export function makeRotateAroundPoint( point, quat, target ) {
6+
7+
target.makeTranslation( - point.x, - point.y, - point.z );
8+
9+
_matrix.makeRotationFromQuaternion( quat );
10+
target.premultiply( _matrix );
11+
12+
_matrix.makeTranslation( point.x, point.y, point.z );
13+
target.premultiply( _matrix );
14+
15+
return target;
16+
17+
}

example/r3f/globe.jsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
GlobeControls,
1010
EastNorthUpFrame,
1111
CompassGizmo,
12-
CameraTransition,
1312
} from '3d-tiles-renderer/r3f';
1413

1514
// Plugins
@@ -19,7 +18,6 @@ import {
1918
TileCompressionPlugin,
2019
TilesFadePlugin,
2120
GLTFExtensionsPlugin,
22-
UnloadTilesPlugin,
2321
} from '3d-tiles-renderer/plugins';
2422
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
2523

@@ -29,6 +27,7 @@ import { Environment } from '@react-three/drei';
2927
import { useControls } from 'leva';
3028
import { MathUtils, Vector3 } from 'three';
3129
import { TilesLoadingBar } from './components/TilesLoadingBar.jsx';
30+
import { CameraViewTransition } from './components/CameraViewTransition.jsx';
3231

3332
const dracoLoader = new DRACOLoader().setDecoderPath( 'https://www.gstatic.com/draco/v1/decoders/' );
3433
const vec1 = new Vector3();
@@ -106,11 +105,11 @@ function App() {
106105
<TilesPlugin plugin={ GLTFExtensionsPlugin } dracoLoader={ dracoLoader } />
107106
<TilesPlugin plugin={ TileCompressionPlugin } />
108107
<TilesPlugin plugin={ UpdateOnChangePlugin } />
109-
<TilesPlugin plugin={ UnloadTilesPlugin } />
110108
<TilesPlugin plugin={ TilesFadePlugin } />
111109

112110
{/* Controls */}
113111
<GlobeControls enableDamping={ true } />
112+
<CameraViewTransition mode={ ortho ? 'orthographic' : 'perspective' } />
114113

115114
{/* Attributions */}
116115
<TilesAttributionOverlay />
@@ -132,8 +131,6 @@ function App() {
132131
backgroundBlurriness={ 0.9 }
133132
environmentIntensity={ 1 }
134133
/>
135-
136-
<CameraTransition mode={ ortho ? 'orthographic' : 'perspective' }/>
137134
</Canvas>
138135
);
139136

src/three/controls/CameraTransitionManager.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export class CameraTransitionManager extends EventDispatcher {
7070
this.fixedPoint = new Vector3();
7171
this.duration = 200;
7272
this.autoSync = true;
73+
this.easeFunction = x => x;
7374

7475
this._target = 0;
7576
this._alpha = 0;
@@ -87,7 +88,7 @@ export class CameraTransitionManager extends EventDispatcher {
8788

8889
}
8990

90-
update() {
91+
update( deltaTime = Math.min( this._clock.getDelta(), 64 / 1000 ) ) {
9192

9293
// update transforms
9394
if ( this.autoSync ) {
@@ -98,8 +99,7 @@ export class CameraTransitionManager extends EventDispatcher {
9899

99100
// perform transition
100101
const { perspectiveCamera, orthographicCamera, transitionCamera, camera } = this;
101-
const clock = this._clock;
102-
const delta = clock.getDelta() * 1e3;
102+
const delta = deltaTime * 1e3;
103103

104104
if ( this._alpha !== this._target ) {
105105

@@ -267,7 +267,7 @@ export class CameraTransitionManager extends EventDispatcher {
267267
// alpha === 1 : orthographic
268268

269269
const { perspectiveCamera, orthographicCamera, transitionCamera, fixedPoint } = this;
270-
const alpha = this._alpha;
270+
const alpha = this.easeFunction( this._alpha );
271271

272272
// get the forward vector
273273
_forward.set( 0, 0, - 1 ).transformDirection( orthographicCamera.matrixWorld ).normalize();

0 commit comments

Comments
 (0)