diff --git a/packages/r3f/src/extension/components/useSnapshotEditorCamera.tsx b/packages/r3f/src/extension/components/useSnapshotEditorCamera.tsx index a413e71..0ca1988 100644 --- a/packages/r3f/src/extension/components/useSnapshotEditorCamera.tsx +++ b/packages/r3f/src/extension/components/useSnapshotEditorCamera.tsx @@ -11,6 +11,7 @@ import type {ISheet} from '@theatre/core' import {types} from '@theatre/core' import type {ISheetObject} from '@theatre/core' import {useThree} from '@react-three/fiber' +import type {$IntentionalAny} from '../../types' const camConf = { transform: { @@ -81,6 +82,20 @@ export default function useSnapshotEditorCamera( return [node, orbitControlsRef] } +/** + * This debounce does delay the placement of a keyframe, so you + * need to be wary of it being too long and causing an issue like + * the following: + * + * 1. user moves playhead t 0 + * 2. user changes orbit + * 3. user moves playead to 10 + * 4. user changes orbit again + * + * User expects a keyframe on t0 and t10 + */ +const COMMIT_DEBOUNCE_MS = 200 + function usePassValuesFromOrbitControlsToTheatre( cam: PerspectiveCameraImpl | undefined, orbitControls: OrbitControlsImpl | null, @@ -89,23 +104,52 @@ function usePassValuesFromOrbitControlsToTheatre( useLayoutEffect(() => { if (!cam || orbitControls == null) return - let currentScrub: undefined | IScrub + let currentScrub: + | undefined + | { + /** "debounce" like timer for making commits to the orbit's changes */ + scheduledCommit?: { + timer: $IntentionalAny + // Future, might be possible to remember the position we need to apply this scrub to (when the last 'end' event was received) + // position: number + } + scrub: IScrub + } - let started = false + const scheduleCommit = () => { + if (currentScrub) { + clearTimeout(currentScrub.scheduledCommit?.timer) + const reference = currentScrub // capture current scrub to make sure currentScrub isn't out of date + currentScrub.scheduledCommit = { + timer: setTimeout(() => { + if (reference === currentScrub) { + currentScrub.scrub.commit() + currentScrub = undefined + } + }, COMMIT_DEBOUNCE_MS), + } + } + } const onStart = () => { - started = true - currentScrub = studio.scrub() - } - const onEnd = () => { if (currentScrub) { - currentScrub.commit() + // prevent existing scheduled commit from executing during the start of a rotation or pan. + // This currentScrub will continue being used and will be scheduled again onEnd + clearTimeout(currentScrub.scheduledCommit?.timer) + } else { + // start new scrub + currentScrub = { + scrub: studio.scrub(), + } } - started = false + } + + const onEnd = () => { + scheduleCommit() } const onChange = () => { - if (!started) return + if (!currentScrub) return const p = cam!.position const position = {x: p.x, y: p.y, z: p.z} @@ -118,7 +162,7 @@ function usePassValuesFromOrbitControlsToTheatre( target, } - currentScrub!.capture(({set}) => { + currentScrub.scrub.capture(({set}) => { set(objRef.current!.props.transform, transform) }) } @@ -131,6 +175,11 @@ function usePassValuesFromOrbitControlsToTheatre( orbitControls.removeEventListener('start', onStart) orbitControls.removeEventListener('end', onEnd) orbitControls.removeEventListener('change', onChange) + if (currentScrub) { + // defensively discard in progress changes + currentScrub.scrub.discard() + currentScrub = undefined + } } }, [cam, orbitControls]) }