|
| 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 | +} ); |
0 commit comments