camera controls enhancement
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import { sceneRegistry, useScene } from '@pascal-app/core'
|
import { sceneRegistry, useScene } from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import { Box3, Vector3 } from 'three'
|
import { Box3, Vector3 } from 'three'
|
||||||
|
|
||||||
const tempBox = new Box3()
|
const tempBox = new Box3()
|
||||||
@@ -28,7 +28,7 @@ export const ViewerCameraControls = () => {
|
|||||||
: CameraControlsImpl.ACTION.DOLLY
|
: CameraControlsImpl.ACTION.DOLLY
|
||||||
|
|
||||||
return {
|
return {
|
||||||
left: CameraControlsImpl.ACTION.NONE,
|
left: CameraControlsImpl.ACTION.SCREEN_PAN,
|
||||||
middle: CameraControlsImpl.ACTION.SCREEN_PAN,
|
middle: CameraControlsImpl.ACTION.SCREEN_PAN,
|
||||||
right: CameraControlsImpl.ACTION.ROTATE,
|
right: CameraControlsImpl.ACTION.ROTATE,
|
||||||
wheel: wheelAction,
|
wheel: wheelAction,
|
||||||
@@ -59,7 +59,7 @@ export const ViewerCameraControls = () => {
|
|||||||
target[0],
|
target[0],
|
||||||
target[1],
|
target[1],
|
||||||
target[2],
|
target[2],
|
||||||
true
|
true,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ export const ViewerCameraControls = () => {
|
|||||||
const cameraPos = new Vector3(
|
const cameraPos = new Vector3(
|
||||||
tempCenter.x + distance * 0.7,
|
tempCenter.x + distance * 0.7,
|
||||||
tempCenter.y + distance * 0.5,
|
tempCenter.y + distance * 0.5,
|
||||||
tempCenter.z + distance * 0.7
|
tempCenter.z + distance * 0.7,
|
||||||
)
|
)
|
||||||
|
|
||||||
controls.current.setLookAt(
|
controls.current.setLookAt(
|
||||||
@@ -91,10 +91,18 @@ export const ViewerCameraControls = () => {
|
|||||||
tempCenter.x,
|
tempCenter.x,
|
||||||
tempCenter.y,
|
tempCenter.y,
|
||||||
tempCenter.z,
|
tempCenter.z,
|
||||||
true
|
true,
|
||||||
)
|
)
|
||||||
}, [targetNodeId, nodes])
|
}, [targetNodeId, nodes])
|
||||||
|
|
||||||
|
const onTransitionStart = useCallback(() => {
|
||||||
|
useViewer.getState().setCameraDragging(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onRest = useCallback(() => {
|
||||||
|
useViewer.getState().setCameraDragging(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CameraControls
|
<CameraControls
|
||||||
ref={controls}
|
ref={controls}
|
||||||
@@ -103,6 +111,10 @@ export const ViewerCameraControls = () => {
|
|||||||
maxPolarAngle={Math.PI / 2 - 0.1}
|
maxPolarAngle={Math.PI / 2 - 0.1}
|
||||||
minPolarAngle={0}
|
minPolarAngle={0}
|
||||||
mouseButtons={mouseButtons}
|
mouseButtons={mouseButtons}
|
||||||
|
onTransitionStart={onTransitionStart}
|
||||||
|
onRest={onRest}
|
||||||
|
restThreshold={0.01}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { type CameraControlEvent, emitter, sceneRegistry, useScene } from '@pascal-app/core'
|
import { type CameraControlEvent, emitter, sceneRegistry, useScene } from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import { Vector3 } from 'three'
|
import { Vector3 } from 'three'
|
||||||
|
|
||||||
const currentTarget = new Vector3()
|
const currentTarget = new Vector3()
|
||||||
@@ -51,6 +51,86 @@ export const CustomCameraControls = () => {
|
|||||||
}
|
}
|
||||||
}, [cameraMode])
|
}, [cameraMode])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('ohla')
|
||||||
|
const keyState = {
|
||||||
|
shiftRight: false,
|
||||||
|
shiftLeft: false,
|
||||||
|
controlRight: false,
|
||||||
|
controlLeft: false,
|
||||||
|
space: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConfig = () => {
|
||||||
|
if (!controls.current) return
|
||||||
|
|
||||||
|
const shift = keyState.shiftRight || keyState.shiftLeft
|
||||||
|
const control = keyState.controlRight || keyState.controlLeft
|
||||||
|
const space = keyState.space
|
||||||
|
|
||||||
|
const wheelAction =
|
||||||
|
cameraMode === 'orthographic'
|
||||||
|
? CameraControlsImpl.ACTION.ZOOM
|
||||||
|
: CameraControlsImpl.ACTION.DOLLY
|
||||||
|
controls.current.mouseButtons.wheel = wheelAction
|
||||||
|
controls.current.mouseButtons.left = CameraControlsImpl.ACTION.NONE
|
||||||
|
controls.current.mouseButtons.middle = CameraControlsImpl.ACTION.SCREEN_PAN
|
||||||
|
controls.current.mouseButtons.right = CameraControlsImpl.ACTION.ROTATE
|
||||||
|
if (space) {
|
||||||
|
controls.current.mouseButtons.left = CameraControlsImpl.ACTION.SCREEN_PAN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.code === 'Space') {
|
||||||
|
keyState.space = true
|
||||||
|
document.body.style.cursor = 'grab'
|
||||||
|
}
|
||||||
|
if (event.code === 'ShiftRight') {
|
||||||
|
keyState.shiftRight = true
|
||||||
|
}
|
||||||
|
if (event.code === 'ShiftLeft') {
|
||||||
|
keyState.shiftLeft = true
|
||||||
|
}
|
||||||
|
if (event.code === 'ControlRight') {
|
||||||
|
keyState.controlRight = true
|
||||||
|
}
|
||||||
|
if (event.code === 'ControlLeft') {
|
||||||
|
keyState.controlLeft = true
|
||||||
|
}
|
||||||
|
updateConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyUp = (event: KeyboardEvent) => {
|
||||||
|
if (event.code === 'Space') {
|
||||||
|
keyState.space = false
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
}
|
||||||
|
if (event.code === 'ShiftRight') {
|
||||||
|
keyState.shiftRight = false
|
||||||
|
}
|
||||||
|
if (event.code === 'ShiftLeft') {
|
||||||
|
keyState.shiftLeft = false
|
||||||
|
}
|
||||||
|
if (event.code === 'ControlRight') {
|
||||||
|
keyState.controlRight = false
|
||||||
|
}
|
||||||
|
if (event.code === 'ControlLeft') {
|
||||||
|
keyState.controlLeft = false
|
||||||
|
}
|
||||||
|
updateConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', onKeyDown)
|
||||||
|
document.addEventListener('keyup', onKeyUp)
|
||||||
|
updateConfig()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', onKeyDown)
|
||||||
|
document.removeEventListener('keyup', onKeyUp)
|
||||||
|
}
|
||||||
|
}, [cameraMode])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleNodeCapture = ({ nodeId }: CameraControlEvent) => {
|
const handleNodeCapture = ({ nodeId }: CameraControlEvent) => {
|
||||||
if (!controls.current) return
|
if (!controls.current) return
|
||||||
@@ -139,6 +219,14 @@ export const CustomCameraControls = () => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const onTransitionStart = useCallback(() => {
|
||||||
|
useViewer.getState().setCameraDragging(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onRest = useCallback(() => {
|
||||||
|
useViewer.getState().setCameraDragging(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CameraControls
|
<CameraControls
|
||||||
makeDefault
|
makeDefault
|
||||||
@@ -148,6 +236,9 @@ export const CustomCameraControls = () => {
|
|||||||
minPolarAngle={0}
|
minPolarAngle={0}
|
||||||
ref={controls}
|
ref={controls}
|
||||||
mouseButtons={mouseButtons}
|
mouseButtons={mouseButtons}
|
||||||
|
onTransitionStart={onTransitionStart}
|
||||||
|
onRest={onRest}
|
||||||
|
restThreshold={0.01}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { type EventSuffix, emitter, type GridEvent } from '@pascal-app/core'
|
import { type EventSuffix, emitter, type GridEvent } from '@pascal-app/core'
|
||||||
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { useThree } from '@react-three/fiber'
|
import { useThree } from '@react-three/fiber'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { Plane, Raycaster, Vector2, Vector3 } from 'three'
|
import { Plane, Raycaster, Vector2, Vector3 } from 'three'
|
||||||
@@ -53,29 +54,35 @@ export function useGridEvents(gridY: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handlePointerDown = (e: PointerEvent) => {
|
const handlePointerDown = (e: PointerEvent) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
emit('pointerdown', e)
|
emit('pointerdown', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePointerUp = (e: PointerEvent) => {
|
const handlePointerUp = (e: PointerEvent) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
emit('pointerup', e)
|
emit('pointerup', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = (e: PointerEvent) => {
|
const handleClick = (e: PointerEvent) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
emit('click', e)
|
emit('click', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePointerMove = (e: PointerEvent) => {
|
const handlePointerMove = (e: PointerEvent) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
emit('move', e)
|
emit('move', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDoubleClick = (e: MouseEvent) => {
|
const handleDoubleClick = (e: MouseEvent) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
emit('double-click', e)
|
emit('double-click', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContextMenu = (e: MouseEvent) => {
|
const handleContextMenu = (e: MouseEvent) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
emit('context-menu', e)
|
emit('context-menu', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,5 +102,5 @@ export function useGridEvents(gridY: number) {
|
|||||||
canvas.removeEventListener('dblclick', handleDoubleClick)
|
canvas.removeEventListener('dblclick', handleDoubleClick)
|
||||||
canvas.removeEventListener('contextmenu', handleContextMenu)
|
canvas.removeEventListener('contextmenu', handleContextMenu)
|
||||||
}
|
}
|
||||||
}, [camera, gl, gridY])
|
}, [camera, gl])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
import {
|
import {
|
||||||
type AnyNode,
|
type AnyNode,
|
||||||
type BuildingNode,
|
type BuildingNode,
|
||||||
|
emitter,
|
||||||
type ItemNode,
|
type ItemNode,
|
||||||
type LevelNode,
|
type LevelNode,
|
||||||
type NodeEvent,
|
type NodeEvent,
|
||||||
type WallNode,
|
|
||||||
type ZoneNode,
|
|
||||||
emitter,
|
|
||||||
pointInPolygon,
|
pointInPolygon,
|
||||||
sceneRegistry,
|
sceneRegistry,
|
||||||
useScene,
|
useScene,
|
||||||
|
type WallNode,
|
||||||
|
type ZoneNode,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import { useThree } from '@react-three/fiber'
|
import { useThree } from '@react-three/fiber'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
@@ -23,14 +23,23 @@ const tempWorldPos = new Vector3()
|
|||||||
// Tolerance for edge detection (in meters)
|
// Tolerance for edge detection (in meters)
|
||||||
const EDGE_TOLERANCE = 0.5
|
const EDGE_TOLERANCE = 0.5
|
||||||
|
|
||||||
type SelectableNodeType = 'building' | 'level' | 'zone' | 'wall' | 'item' | 'slab' | 'ceiling' | 'roof'
|
type SelectableNodeType =
|
||||||
|
| 'building'
|
||||||
|
| 'level'
|
||||||
|
| 'zone'
|
||||||
|
| 'wall'
|
||||||
|
| 'item'
|
||||||
|
| 'slab'
|
||||||
|
| 'ceiling'
|
||||||
|
| 'roof'
|
||||||
|
|
||||||
// Expand polygon outward by a small amount to include items on edges
|
// Expand polygon outward by a small amount to include items on edges
|
||||||
const expandPolygon = (polygon: [number, number][], tolerance: number): [number, number][] => {
|
const expandPolygon = (polygon: [number, number][], tolerance: number): [number, number][] => {
|
||||||
if (polygon.length < 3) return polygon
|
if (polygon.length < 3) return polygon
|
||||||
|
|
||||||
// Calculate centroid
|
// Calculate centroid
|
||||||
let cx = 0, cz = 0
|
let cx = 0,
|
||||||
|
cz = 0
|
||||||
for (const [x, z] of polygon) {
|
for (const [x, z] of polygon) {
|
||||||
cx += x
|
cx += x
|
||||||
cz += z
|
cz += z
|
||||||
@@ -50,7 +59,11 @@ const expandPolygon = (polygon: [number, number][], tolerance: number): [number,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if point is in polygon with tolerance for edges
|
// Check if point is in polygon with tolerance for edges
|
||||||
const pointInPolygonWithTolerance = (x: number, z: number, polygon: [number, number][]): boolean => {
|
const pointInPolygonWithTolerance = (
|
||||||
|
x: number,
|
||||||
|
z: number,
|
||||||
|
polygon: [number, number][],
|
||||||
|
): boolean => {
|
||||||
// First try exact check
|
// First try exact check
|
||||||
if (pointInPolygon(x, z, polygon)) return true
|
if (pointInPolygon(x, z, polygon)) return true
|
||||||
// Then try with expanded polygon for edge tolerance
|
// Then try with expanded polygon for edge tolerance
|
||||||
@@ -186,7 +199,9 @@ const getStrategy = (): SelectionStrategy | null => {
|
|||||||
const { selectedIds } = useViewer.getState().selection
|
const { selectedIds } = useViewer.getState().selection
|
||||||
// Toggle selection - if already selected, deselect; otherwise select
|
// Toggle selection - if already selected, deselect; otherwise select
|
||||||
if (selectedIds.includes(node.id)) {
|
if (selectedIds.includes(node.id)) {
|
||||||
useViewer.getState().setSelection({ selectedIds: selectedIds.filter((id) => id !== node.id) })
|
useViewer
|
||||||
|
.getState()
|
||||||
|
.setSelection({ selectedIds: selectedIds.filter((id) => id !== node.id) })
|
||||||
} else {
|
} else {
|
||||||
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
||||||
}
|
}
|
||||||
@@ -244,7 +259,16 @@ export const SelectionManager = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe to all node types
|
// Subscribe to all node types
|
||||||
const allTypes: SelectableNodeType[] = ['building', 'level', 'zone', 'wall', 'item', 'slab', 'ceiling', 'roof']
|
const allTypes: SelectableNodeType[] = [
|
||||||
|
'building',
|
||||||
|
'level',
|
||||||
|
'zone',
|
||||||
|
'wall',
|
||||||
|
'item',
|
||||||
|
'slab',
|
||||||
|
'ceiling',
|
||||||
|
'roof',
|
||||||
|
]
|
||||||
for (const type of allTypes) {
|
for (const type of allTypes) {
|
||||||
emitter.on(`${type}:enter`, onEnter)
|
emitter.on(`${type}:enter`, onEnter)
|
||||||
emitter.on(`${type}:leave`, onLeave)
|
emitter.on(`${type}:leave`, onLeave)
|
||||||
@@ -258,7 +282,7 @@ export const SelectionManager = () => {
|
|||||||
emitter.off(`${type}:click`, onClick)
|
emitter.off(`${type}:click`, onClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selection])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -268,12 +292,17 @@ export const SelectionManager = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PointerMissedHandler = ({ clickHandledRef }: { clickHandledRef: React.MutableRefObject<boolean> }) => {
|
const PointerMissedHandler = ({
|
||||||
|
clickHandledRef,
|
||||||
|
}: {
|
||||||
|
clickHandledRef: React.MutableRefObject<boolean>
|
||||||
|
}) => {
|
||||||
const gl = useThree((s) => s.gl)
|
const gl = useThree((s) => s.gl)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClick = (event: MouseEvent) => {
|
const handleClick = (event: MouseEvent) => {
|
||||||
// Only handle left clicks
|
// Only handle left clicks
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (event.button !== 0) return
|
if (event.button !== 0) return
|
||||||
|
|
||||||
// Use requestAnimationFrame to check after R3F event handlers
|
// Use requestAnimationFrame to check after R3F event handlers
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
type ZoneNode,
|
type ZoneNode,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import type { ThreeEvent } from '@react-three/fiber'
|
import type { ThreeEvent } from '@react-three/fiber'
|
||||||
|
import useViewer from '../store/use-viewer';
|
||||||
|
|
||||||
type NodeConfig = {
|
type NodeConfig = {
|
||||||
site: { node: SiteNode; event: SiteEvent }
|
site: { node: SiteNode; event: SiteEvent }
|
||||||
@@ -54,21 +55,24 @@ export function useNodeEvents<T extends NodeType>(node: NodeConfig[T]['node'], t
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
emit('pointerdown', e)
|
emit('pointerdown', e)
|
||||||
},
|
},
|
||||||
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
emit('pointerup', e)
|
emit('pointerup', e)
|
||||||
},
|
},
|
||||||
onClick: (e: ThreeEvent<PointerEvent>) => {
|
onClick: (e: ThreeEvent<PointerEvent>) => {
|
||||||
|
if (useViewer.getState().cameraDragging) return
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return
|
||||||
emit('click', e)
|
emit('click', e)
|
||||||
},
|
},
|
||||||
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit('enter', e),
|
onPointerEnter: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('enter', e)},
|
||||||
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit('leave', e),
|
onPointerLeave: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('leave', e)},
|
||||||
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit('move', e),
|
onPointerMove: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('move', e)},
|
||||||
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit('double-click', e),
|
onDoubleClick: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('double-click', e)},
|
||||||
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit('context-menu', e),
|
onContextMenu: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('context-menu', e)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ type ViewerState = {
|
|||||||
// Export functionality
|
// Export functionality
|
||||||
exportScene: (() => Promise<void>) | null
|
exportScene: (() => Promise<void>) | null
|
||||||
setExportScene: (fn: (() => Promise<void>) | null) => void
|
setExportScene: (fn: (() => Promise<void>) | null) => void
|
||||||
|
|
||||||
|
cameraDragging: boolean
|
||||||
|
setCameraDragging: (dragging: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const useViewer = create<ViewerState>()((set, get) => ({
|
const useViewer = create<ViewerState>()((set, get) => ({
|
||||||
@@ -107,6 +110,9 @@ const useViewer = create<ViewerState>()((set, get) => ({
|
|||||||
|
|
||||||
exportScene: null,
|
exportScene: null,
|
||||||
setExportScene: (fn) => set({ exportScene: fn }),
|
setExportScene: (fn) => set({ exportScene: fn }),
|
||||||
|
|
||||||
|
cameraDragging: false,
|
||||||
|
setCameraDragging: (dragging) => set({ cameraDragging: dragging }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useViewer;
|
export default useViewer;
|
||||||
|
|||||||
Reference in New Issue
Block a user