How can I smooth transition perspectiveCamera.lookAt(vector) on Click to next marker? #807
-
Hello, Have a great time of the day! I'm making a game to prove the concept. Currently I have a plane with a grid of polygons. Each polygon in the scene has unique center coordinates. I've positioned the camera, and I wrote onClick handler for each polygon, so it updates redux with new coordinates for Camera.lookAt() The movement of the camera is instant, and do not feels natural, so I want to add a smooth transition on redux values change. I guess that I could use I can't find docs for @react-spring/three, and can't install react-spring/three - it is removed from npm How do I transition camera.lookAt() smoothly from [0, 0, 0] to [5, -5, 0]? I have my CameraController component: import * as React from 'react'
import { Vector3, PerspectiveCamera } from 'three'
import { useSelector } from 'react-redux'
import { useFrame, useThree } from 'react-three-fiber'
import type { ReduxState } from 'src/types/common'
import type { Cube } from 'src/types/game'
interface SelectorProps {
lookAt: Cube
position: Cube
}
function selector(state: ReduxState): SelectorProps {
return {
lookAt: state.camera.lookAt,
position: state.camera.position,
}
}
function CameraController(): JSX.Element {
const ref = React.useRef<PerspectiveCamera | null>(null)
const { lookAt, position } = useSelector(selector)
const vectorLookAt = React.useMemo(
function memo() {
return new Vector3(lookAt.x, lookAt.y, lookAt.z)
},
[lookAt.x, lookAt.y, lookAt.z]
)
const vectorPosition = React.useMemo(
function memo() {
return new Vector3(position.x, position.y, position.z)
},
[position.x, position.y, position.z]
)
const { setDefaultCamera } = useThree()
// Make the camera known to the system
React.useEffect(
function effect() {
if (ref.current !== null) {
setDefaultCamera(ref.current)
ref.current.lookAt(vectorLookAt)
ref.current.updateMatrixWorld()
}
},
[vectorLookAt, setDefaultCamera]
)
// Update it every frame
useFrame(function renderCallback() {
if (ref.current !== null) {
ref.current.updateMatrixWorld()
}
})
return <perspectiveCamera ref={ref} fov={75} position={vectorPosition} />
}
export default React.memo(CameraController) and the Tile component which dispatches an action to redux to update the camera lookAt position to it's own coordinates onClick import * as React from 'react'
import { Vector3, Object3D, Shape, DoubleSide } from 'three'
import { useSelector, useDispatch } from 'react-redux'
import { cameraLookAt } from 'src/redux/actions/camera'
import type { ReduxState } from 'src/types/common'
import type {
Vertex,
PieceId,
} from 'src/types/game'
interface Props {
// id: VertexId
cube: Vertex
position: Vector3
color: string
vertices: Vertex[]
}
function Tile({ position, cube, color, vertices }: Props): JSX.Element {
const ref = React.useRef<Object3D>()
const dispatch = useDispatch()
const onClick = React.useCallback(
function callback() {
dispatch(cameraLookAt({ cube }))
},
[dispatch, cube]
)
const args: [
shapes: Shape | Shape[],
curveSegments?: number | undefined
] = React.useMemo(
function memo() {
const shape = new Shape()
shape.moveTo(vertices[0].x, vertices[0].y)
shape.lineTo(vertices[1].x, vertices[1].y)
shape.lineTo(vertices[2].x, vertices[2].y)
shape.lineTo(vertices[3].x, vertices[3].y)
shape.lineTo(vertices[4].x, vertices[4].y)
shape.lineTo(vertices[5].x, vertices[5].y)
shape.lineTo(vertices[0].x, vertices[0].y)
return [shape]
},
[vertices]
)
return (
<mesh
castShadow
receiveShadow
position={position}
ref={ref}
onClick={onClick}
onPointerOver={onPointerOver}
onPointerOut={onPointerOut}
>
<shapeBufferGeometry args={args} />
<meshBasicMaterial
color={color}
side={DoubleSide}
/>
</mesh>
)
}
export default React.memo(Tile) PS:I have noticed that I also want to use the same redux.js logic to smoothly transition camera position around current lookAt by the curve then players switch turns. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 2 replies
-
function CameraController() {
const ref = useRef()
const { lookAt, position } = useSelector(selector)
const vectorLookAt = useState(() => new THREE.Vector3())
useFrame(() => {
ref.current.lookAt(vectorLookAt.lerp(lookAt, 0.1))
ref.current.position.lerp(position, 0.1)
})
return <perspectiveCamera ref={ref} ... /> perspectiveCam is typed in the jsx IntrinsicElements. i dont know how types work, you can sure take a look. |
Beta Was this translation helpful? Give feedback.
-
@drcmda Even with On each mouse click I got one I run the profiler and I got next screenshot: and I saved json file, which I can send by email if you need. Basically each click is creating long running task, which executes for ~191ms, and involves 2 click events: one is natural, and second is synthetic react event. This is the reason for animation staggering. Is there any advice to how get rid of long running process? |
Beta Was this translation helpful? Give feedback.
-
I made a zoom on one of clicks, and I see that main bottle neck is 3 redux action dispatches. I think I can manage to make a single dispatch action, which will update 3 reducers at once. I use immer under the hood, which in theory should provide immutability and performance gains, but something somewhere went wrong. |
Beta Was this translation helpful? Give feedback.
-
useframe is called 60 times per second all the time unless your canvas is set to invalidateFrameloop, in which case the user is responsible for invalidating. |
Beta Was this translation helpful? Give feedback.
-
final working code for smooth camera transition: import * as React from 'react'
import { Vector3, PerspectiveCamera } from 'three'
import { useSelector } from 'react-redux'
import { useThree, useFrame } from 'react-three-fiber'
import type { ReduxState } from 'src/types/common'
import type { Cube } from 'src/types/game'
interface SelectorProps {
prevLookAt: Cube
lookAt: Cube
position: Cube
}
function selector(state: ReduxState): SelectorProps {
return {
prevLookAt: state.camera.prevLookAt,
lookAt: state.camera.lookAt,
position: state.camera.position,
}
}
function CameraController(): JSX.Element {
const ref = React.useRef<PerspectiveCamera | null>(null)
const { prevLookAt, lookAt, position } = useSelector(selector)
const prevVectorLookAt = React.useMemo(
function memo() {
return new Vector3(prevLookAt.x, prevLookAt.y, prevLookAt.z)
},
[prevLookAt.x, prevLookAt.y, prevLookAt.z]
)
const vectorLookAt = React.useMemo(
function memo() {
return new Vector3(lookAt.x, lookAt.y, lookAt.z)
},
[lookAt.x, lookAt.y, lookAt.z]
)
const vectorPosition = React.useMemo(
function memo() {
return new Vector3(position.x, position.y, position.z)
},
[position.x, position.y, position.z]
)
const { setDefaultCamera } = useThree()
// Make the camera known to the system
React.useEffect(
function effect() {
if (ref.current !== null) {
setDefaultCamera(ref.current)
}
},
[setDefaultCamera]
)
// const requestRef = React.useRef<number>(0)
// const animate = React.useCallback(
// function callback(/* time */): void {
// // console.info('animate')
// if (ref.current !== null) {
// ref.current.lookAt(prevVectorLookAt.lerp(vectorLookAt, 0.1))
// ref.current.updateMatrixWorld()
// // console.info('updateMatrixWorld')
// }
// requestRef.current = requestAnimationFrame(animate)
// },
// [prevVectorLookAt, vectorLookAt]
// )
// React.useEffect(() => {
// animate()
// requestRef.current = window.requestAnimationFrame(animate)
// return () => window.cancelAnimationFrame(requestRef.current)
// }, [animate]) // Make sure the effect runs only once
// Update it every frame
useFrame(function renderCallback() {
if (ref.current !== null) {
ref.current.lookAt(prevVectorLookAt.lerp(vectorLookAt, 0.1))
ref.current.updateMatrixWorld()
}
})
return <perspectiveCamera ref={ref} fov={75} position={vectorPosition} />
}
export default React.memo(CameraController) |
Beta Was this translation helpful? Give feedback.
useframe is called 60 times per second all the time unless your canvas is set to invalidateFrameloop, in which case the user is responsible for invalidating.