r3f fix: Pinching creates more than one undo level in r3f P-202
See https://linear.app/theatre/issue/P-202/pinching-creates-more-than-one-undo-level-in-r3f-history-bug
This commit is contained in:
parent
56a059fdd2
commit
8bd37a28d6
1 changed files with 59 additions and 10 deletions
|
@ -11,6 +11,7 @@ import type {ISheet} from '@theatre/core'
|
||||||
import {types} from '@theatre/core'
|
import {types} from '@theatre/core'
|
||||||
import type {ISheetObject} from '@theatre/core'
|
import type {ISheetObject} from '@theatre/core'
|
||||||
import {useThree} from '@react-three/fiber'
|
import {useThree} from '@react-three/fiber'
|
||||||
|
import type {$IntentionalAny} from '../../types'
|
||||||
|
|
||||||
const camConf = {
|
const camConf = {
|
||||||
transform: {
|
transform: {
|
||||||
|
@ -81,6 +82,20 @@ export default function useSnapshotEditorCamera(
|
||||||
return [node, orbitControlsRef]
|
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(
|
function usePassValuesFromOrbitControlsToTheatre(
|
||||||
cam: PerspectiveCameraImpl | undefined,
|
cam: PerspectiveCameraImpl | undefined,
|
||||||
orbitControls: OrbitControlsImpl | null,
|
orbitControls: OrbitControlsImpl | null,
|
||||||
|
@ -89,23 +104,52 @@ function usePassValuesFromOrbitControlsToTheatre(
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!cam || orbitControls == null) return
|
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 = () => {
|
const onStart = () => {
|
||||||
started = true
|
|
||||||
currentScrub = studio.scrub()
|
|
||||||
}
|
|
||||||
const onEnd = () => {
|
|
||||||
if (currentScrub) {
|
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 = () => {
|
const onChange = () => {
|
||||||
if (!started) return
|
if (!currentScrub) return
|
||||||
|
|
||||||
const p = cam!.position
|
const p = cam!.position
|
||||||
const position = {x: p.x, y: p.y, z: p.z}
|
const position = {x: p.x, y: p.y, z: p.z}
|
||||||
|
@ -118,7 +162,7 @@ function usePassValuesFromOrbitControlsToTheatre(
|
||||||
target,
|
target,
|
||||||
}
|
}
|
||||||
|
|
||||||
currentScrub!.capture(({set}) => {
|
currentScrub.scrub.capture(({set}) => {
|
||||||
set(objRef.current!.props.transform, transform)
|
set(objRef.current!.props.transform, transform)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -131,6 +175,11 @@ function usePassValuesFromOrbitControlsToTheatre(
|
||||||
orbitControls.removeEventListener('start', onStart)
|
orbitControls.removeEventListener('start', onStart)
|
||||||
orbitControls.removeEventListener('end', onEnd)
|
orbitControls.removeEventListener('end', onEnd)
|
||||||
orbitControls.removeEventListener('change', onChange)
|
orbitControls.removeEventListener('change', onChange)
|
||||||
|
if (currentScrub) {
|
||||||
|
// defensively discard in progress changes
|
||||||
|
currentScrub.scrub.discard()
|
||||||
|
currentScrub = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [cam, orbitControls])
|
}, [cam, orbitControls])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue