WIP: Upgrade to THREE 155: Upgrade OrbitControls

This commit is contained in:
Aria Minaei 2023-08-04 10:54:17 +02:00
parent 7a5b1c744c
commit 3837e179bb
14 changed files with 202 additions and 166 deletions

View file

@ -31,13 +31,14 @@
"@types/node": "^15.6.2",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@types/styled-components": "^5.1.26",
"@vitejs/plugin-react": "^4.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"fast-glob": "^3.3.0",
"lodash-es": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^5.3.5",
"styled-components": "^5.3.11",
"theatric": "workspace:*",
"three": "^0.155.0",
"typescript": "^4.4.2",

View file

@ -10,7 +10,7 @@ import {
RawShaderMaterial,
Scene,
ShaderMaterial,
SphereBufferGeometry,
SphereGeometry,
Vector2,
Vector3,
WebGLRenderer,
@ -190,7 +190,7 @@ export default function ThreeScene(props: ThreeSceneProps) {
light.position.set(1, 5, 4)
scene.add(light)
mesh = new Mesh(new SphereBufferGeometry(3), new MeshPhongMaterial())
mesh = new Mesh(new SphereGeometry(3), new MeshPhongMaterial())
scene.add(mesh)
// RAF

View file

@ -56,8 +56,8 @@
"@types/node": "^15.6.2",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@types/styled-components": "^5.1.9",
"@types/three": "0.131.0",
"@types/styled-components": "^5.1.26",
"@types/three": "0.155.0",
"esbuild": "^0.18.17",
"esbuild-register": "^3.4.2",
"lodash-es": "^4.17.21",
@ -66,18 +66,18 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.2.0",
"react-merge-refs": "^1.1.0",
"react-shadow": "^19.0.3",
"react-use-measure": "^2.0.4",
"react-merge-refs": "^2.0.2",
"react-shadow": "^20.4.0",
"react-use-measure": "^2.1.1",
"reakit": "^1.3.8",
"styled-components": "^5.3.5",
"styled-components": "^5.3.11",
"three": "0.155.0",
"three-stdlib": "^2.24.1",
"typescript": "^4.4.2",
"zustand": "^3.5.1"
},
"peerDependencies": {
"@react-three/fiber": ">=7.0.6",
"@react-three/fiber": "^8.13.6",
"@theatre/core": "*",
"@theatre/studio": "*",
"react": ">=17.0.2",

View file

@ -4,7 +4,7 @@ import type {
Object3D,
} from 'three'
import {useFrame, useThree} from '@react-three/fiber'
import mergeRefs from 'react-merge-refs'
import {mergeRefs} from 'react-merge-refs'
import {editable} from '../index'
import {Vector3} from 'three'
import type {MutableRefObject} from 'react'

View file

@ -1,7 +1,7 @@
import * as React from 'react'
import type {PerspectiveCamera as PerspectiveCameraImpl, Object3D} from 'three'
import {useFrame, useThree} from '@react-three/fiber'
import mergeRefs from 'react-merge-refs'
import {mergeRefs} from 'react-merge-refs'
import {editable} from '../index'
import {Vector3} from 'three'
import {editorStore} from '../main/store'

View file

@ -1,8 +1,6 @@
/* eslint-disable */
import type {Matrix4} from 'three'
import {
Camera,
EventDispatcher,
Matrix4,
MOUSE,
OrthographicCamera,
PerspectiveCamera,
@ -13,6 +11,9 @@ import {
Vector3,
} from 'three'
// Almost an exact copy of https://github.com/pmndrs/three-stdlib/blob/4c04593ee49bb0b022025718844f3ce2b21f67bf/src/controls/OrbitControls.ts
// The only change is that we added `(if (altKey)` at line 866 to only rotate if alt key is pressed
// This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
//
@ -23,8 +24,8 @@ import {
const moduloWrapAround = (offset: number, capacity: number) =>
((offset % capacity) + capacity) % capacity
class OrbitControlsImpl extends EventDispatcher {
object: Camera
class OrbitControls extends EventDispatcher {
object: PerspectiveCamera | OrthographicCamera
domElement: HTMLElement | undefined
// Set to false to disable this control
enabled = true
@ -73,13 +74,20 @@ class OrbitControlsImpl extends EventDispatcher {
BOTTOM: 'ArrowDown',
}
// Mouse buttons
mouseButtons = {
mouseButtons: Partial<{
LEFT: MOUSE
MIDDLE: MOUSE
RIGHT: MOUSE
}> = {
LEFT: MOUSE.ROTATE,
MIDDLE: MOUSE.DOLLY,
RIGHT: MOUSE.PAN,
}
// Touch fingers
touches = {ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN}
touches: Partial<{
ONE: TOUCH
TWO: TOUCH
}> = {ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN}
target0: Vector3
position0: Vector3
zoom0: number
@ -93,13 +101,17 @@ class OrbitControlsImpl extends EventDispatcher {
getDistance: () => number
listenToKeyEvents: (domElement: HTMLElement) => void
stopListenToKeyEvents: () => void
saveState: () => void
reset: () => void
update: () => void
connect: (domElement: HTMLElement) => void
dispose: () => void
constructor(object: Camera, domElement?: HTMLElement) {
constructor(
object: PerspectiveCamera | OrthographicCamera,
domElement?: HTMLElement,
) {
super()
this.object = object
@ -108,7 +120,7 @@ class OrbitControlsImpl extends EventDispatcher {
// for reset
this.target0 = this.target.clone()
this.position0 = this.object.position.clone()
this.zoom0 = this.object instanceof PerspectiveCamera ? this.object.zoom : 1
this.zoom0 = this.object.zoom
//
// public methods
@ -166,20 +178,22 @@ class OrbitControlsImpl extends EventDispatcher {
this._domElementKeyEvents = domElement
}
this.stopListenToKeyEvents = (): void => {
this._domElementKeyEvents.removeEventListener('keydown', onKeyDown)
this._domElementKeyEvents = null
}
this.saveState = (): void => {
scope.target0.copy(scope.target)
scope.position0.copy(scope.object.position)
scope.zoom0 =
scope.object instanceof PerspectiveCamera ? scope.object.zoom : 1
scope.zoom0 = scope.object.zoom
}
this.reset = (): void => {
scope.target.copy(scope.target0)
scope.object.position.copy(scope.position0)
if (scope.object instanceof PerspectiveCamera) {
scope.object.zoom = scope.zoom0
scope.object.updateProjectionMatrix()
}
scope.object.zoom = scope.zoom0
scope.object.updateProjectionMatrix()
scope.dispatchEvent(changeEvent)
@ -191,12 +205,10 @@ class OrbitControlsImpl extends EventDispatcher {
// this method is exposed, but perhaps it would be better if we can make it private...
this.update = ((): (() => void) => {
const offset = new Vector3()
const up = new Vector3(0, 1, 0)
// so camera.up is the orbit axis
const quat = new Quaternion().setFromUnitVectors(
object.up,
new Vector3(0, 1, 0),
)
const quat = new Quaternion().setFromUnitVectors(object.up, up)
const quatInverse = quat.clone().invert()
const lastPosition = new Vector3()
@ -207,6 +219,10 @@ class OrbitControlsImpl extends EventDispatcher {
return function update(): boolean {
const position = scope.object.position
// update new up direction
quat.setFromUnitVectors(object.up, up)
quatInverse.copy(quat).invert()
offset.copy(position).sub(scope.target)
// rotate offset to "y-axis-is-up" space
@ -1059,8 +1075,11 @@ class OrbitControlsImpl extends EventDispatcher {
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
// Pan - left mouse, or arrow keys / touch: one-finger move
class MapControls extends OrbitControlsImpl {
constructor(object: Camera, domElement?: HTMLElement) {
class MapControls extends OrbitControls {
constructor(
object: PerspectiveCamera | OrthographicCamera,
domElement?: HTMLElement,
) {
super(object, domElement)
this.screenSpacePanning = false // pan orthogonal to world-space direction camera.up
@ -1073,4 +1092,4 @@ class MapControls extends OrbitControlsImpl {
}
}
export {OrbitControlsImpl, MapControls}
export {OrbitControls as OrbitControlsImpl, MapControls}

View file

@ -1,9 +1,12 @@
import type {EventManager, ReactThreeFiber} from '@react-three/fiber'
import {useFrame, useThree} from '@react-three/fiber'
import * as React from 'react'
import {forwardRef, useEffect, useMemo} from 'react'
import type {Camera, Event} from 'three'
import {OrbitControlsImpl as OrbitControlsImpl} from './OrbitControlsImpl'
import {OrbitControlsImpl} from './OrbitControlsImpl'
export type OrbitControlsChangeEvent = Event & {
target: EventTarget & {object: Camera}
}
export type OrbitControlsProps = Omit<
ReactThreeFiber.Overwrite<
@ -13,17 +16,21 @@ export type OrbitControlsProps = Omit<
domElement?: HTMLElement
enableDamping?: boolean
makeDefault?: boolean
onChange?: (e?: Event) => void
onChange?: (e?: OrbitControlsChangeEvent) => void
onEnd?: (e?: Event) => void
onStart?: (e?: Event) => void
regress?: boolean
target?: ReactThreeFiber.Vector3
keyEvents?: boolean | HTMLElement
}
>,
'ref'
>
export const OrbitControls = forwardRef<OrbitControlsImpl, OrbitControlsProps>(
export const OrbitControls = React.forwardRef<
OrbitControlsImpl,
OrbitControlsProps
>(
(
{
makeDefault,
@ -31,6 +38,7 @@ export const OrbitControls = forwardRef<OrbitControlsImpl, OrbitControlsProps>(
regress,
domElement,
enableDamping = true,
keyEvents = false,
onChange,
onStart,
onEnd,
@ -38,56 +46,67 @@ export const OrbitControls = forwardRef<OrbitControlsImpl, OrbitControlsProps>(
},
ref,
) => {
const invalidate = useThree(({invalidate}) => invalidate)
const defaultCamera = useThree(({camera}) => camera)
const gl = useThree(({gl}) => gl)
const events = useThree(({events}) => events) as EventManager<HTMLElement>
const set = useThree(({set}) => set)
const get = useThree(({get}) => get)
const performance = useThree(({performance}) => performance)
const explCamera = camera || defaultCamera
const explDomElement =
domElement ||
(typeof events.connected !== 'boolean' ? events.connected : gl.domElement)
const controls = useMemo(
const invalidate = useThree((state) => state.invalidate)
const defaultCamera = useThree((state) => state.camera)
const gl = useThree((state) => state.gl)
const events = useThree(
(state) => state.events,
) as EventManager<HTMLElement>
const setEvents = useThree((state) => state.setEvents)
const set = useThree((state) => state.set)
const get = useThree((state) => state.get)
const performance = useThree((state) => state.performance)
const explCamera = (camera || defaultCamera) as
| THREE.OrthographicCamera
| THREE.PerspectiveCamera
const explDomElement = (domElement ||
events.connected ||
gl.domElement) as HTMLElement
const controls = React.useMemo(
() => new OrbitControlsImpl(explCamera),
[explCamera],
)
useFrame(() => {
if (controls.enabled) controls.update()
})
}, -1)
useEffect(() => {
const callback = (e: Event) => {
React.useEffect(() => {
if (keyEvents) {
controls.connect(keyEvents === true ? explDomElement : keyEvents)
}
controls.connect(explDomElement)
return () => void controls.dispose()
}, [keyEvents, explDomElement, regress, controls, invalidate])
React.useEffect(() => {
const callback = (e: OrbitControlsChangeEvent) => {
invalidate()
if (regress) performance.regress()
if (onChange) onChange(e)
}
controls.connect(explDomElement!)
controls.addEventListener('change', callback)
const onStartCb = (e: Event) => {
if (onStart) onStart(e)
}
if (onStart) controls.addEventListener('start', onStart)
if (onEnd) controls.addEventListener('end', onEnd)
const onEndCb = (e: Event) => {
if (onEnd) onEnd(e)
}
controls.addEventListener('change', callback)
controls.addEventListener('start', onStartCb)
controls.addEventListener('end', onEndCb)
return () => {
controls.removeEventListener('start', onStartCb)
controls.removeEventListener('end', onEndCb)
controls.removeEventListener('change', callback)
if (onStart) controls.removeEventListener('start', onStart)
if (onEnd) controls.removeEventListener('end', onEnd)
controls.dispose()
}
}, [
explDomElement,
onChange,
onStart,
onEnd,
regress,
controls,
invalidate,
])
}, [onChange, onStart, onEnd, controls, invalidate, setEvents])
useEffect(() => {
React.useEffect(() => {
if (makeDefault) {
const old = get().controls
set({controls})
@ -105,5 +124,4 @@ export const OrbitControls = forwardRef<OrbitControlsImpl, OrbitControlsProps>(
)
},
)
export {OrbitControlsImpl}

View file

@ -2,7 +2,7 @@ import type {ComponentProps, ComponentType, Ref, RefAttributes} from 'react'
import {useMemo, useState} from 'react'
import React, {forwardRef, useEffect, useLayoutEffect, useRef} from 'react'
import {allRegisteredObjects, editorStore} from './store'
import mergeRefs from 'react-merge-refs'
import {mergeRefs} from 'react-merge-refs'
import useInvalidate from './useInvalidate'
import {useCurrentSheet} from './SheetProvider'
import defaultEditableFactoryConfig from './defaultEditableFactoryConfig'