camera controls enhancement
This commit is contained in:
@@ -3,15 +3,15 @@
|
||||
import {
|
||||
type AnyNode,
|
||||
type BuildingNode,
|
||||
emitter,
|
||||
type ItemNode,
|
||||
type LevelNode,
|
||||
type NodeEvent,
|
||||
type WallNode,
|
||||
type ZoneNode,
|
||||
emitter,
|
||||
pointInPolygon,
|
||||
sceneRegistry,
|
||||
useScene,
|
||||
type WallNode,
|
||||
type ZoneNode,
|
||||
} from '@pascal-app/core'
|
||||
import { useThree } from '@react-three/fiber'
|
||||
import { useEffect, useRef } from 'react'
|
||||
@@ -23,14 +23,23 @@ const tempWorldPos = new Vector3()
|
||||
// Tolerance for edge detection (in meters)
|
||||
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
|
||||
const expandPolygon = (polygon: [number, number][], tolerance: number): [number, number][] => {
|
||||
if (polygon.length < 3) return polygon
|
||||
|
||||
// Calculate centroid
|
||||
let cx = 0, cz = 0
|
||||
let cx = 0,
|
||||
cz = 0
|
||||
for (const [x, z] of polygon) {
|
||||
cx += x
|
||||
cz += z
|
||||
@@ -50,7 +59,11 @@ const expandPolygon = (polygon: [number, number][], tolerance: number): [number,
|
||||
}
|
||||
|
||||
// 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
|
||||
if (pointInPolygon(x, z, polygon)) return true
|
||||
// Then try with expanded polygon for edge tolerance
|
||||
@@ -186,7 +199,9 @@ const getStrategy = (): SelectionStrategy | null => {
|
||||
const { selectedIds } = useViewer.getState().selection
|
||||
// Toggle selection - if already selected, deselect; otherwise select
|
||||
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 {
|
||||
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
||||
}
|
||||
@@ -244,7 +259,16 @@ export const SelectionManager = () => {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
emitter.on(`${type}:enter`, onEnter)
|
||||
emitter.on(`${type}:leave`, onLeave)
|
||||
@@ -258,7 +282,7 @@ export const SelectionManager = () => {
|
||||
emitter.off(`${type}:click`, onClick)
|
||||
}
|
||||
}
|
||||
}, [selection])
|
||||
}, [])
|
||||
|
||||
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)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
// Only handle left clicks
|
||||
if (useViewer.getState().cameraDragging) return
|
||||
if (event.button !== 0) return
|
||||
|
||||
// Use requestAnimationFrame to check after R3F event handlers
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
type ZoneNode,
|
||||
} from '@pascal-app/core'
|
||||
import type { ThreeEvent } from '@react-three/fiber'
|
||||
import useViewer from '../store/use-viewer';
|
||||
|
||||
type NodeConfig = {
|
||||
site: { node: SiteNode; event: SiteEvent }
|
||||
@@ -54,21 +55,24 @@ export function useNodeEvents<T extends NodeType>(node: NodeConfig[T]['node'], t
|
||||
|
||||
return {
|
||||
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
||||
if (useViewer.getState().cameraDragging) return
|
||||
if (e.button !== 0) return
|
||||
emit('pointerdown', e)
|
||||
},
|
||||
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
||||
if (useViewer.getState().cameraDragging) return
|
||||
if (e.button !== 0) return
|
||||
emit('pointerup', e)
|
||||
},
|
||||
onClick: (e: ThreeEvent<PointerEvent>) => {
|
||||
if (useViewer.getState().cameraDragging) return
|
||||
if (e.button !== 0) return
|
||||
emit('click', e)
|
||||
},
|
||||
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit('enter', e),
|
||||
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit('leave', e),
|
||||
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit('move', e),
|
||||
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit('double-click', e),
|
||||
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit('context-menu', e),
|
||||
onPointerEnter: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('enter', e)},
|
||||
onPointerLeave: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('leave', e)},
|
||||
onPointerMove: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('move', e)},
|
||||
onDoubleClick: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('double-click', e)},
|
||||
onContextMenu: (e: ThreeEvent<PointerEvent>) => {if (useViewer.getState().cameraDragging) return; emit('context-menu', e)},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ type ViewerState = {
|
||||
// Export functionality
|
||||
exportScene: (() => Promise<void>) | null
|
||||
setExportScene: (fn: (() => Promise<void>) | null) => void
|
||||
|
||||
cameraDragging: boolean
|
||||
setCameraDragging: (dragging: boolean) => void
|
||||
}
|
||||
|
||||
const useViewer = create<ViewerState>()((set, get) => ({
|
||||
@@ -107,6 +110,9 @@ const useViewer = create<ViewerState>()((set, get) => ({
|
||||
|
||||
exportScene: null,
|
||||
setExportScene: (fn) => set({ exportScene: fn }),
|
||||
|
||||
cameraDragging: false,
|
||||
setCameraDragging: (dragging) => set({ cameraDragging: dragging }),
|
||||
}));
|
||||
|
||||
export default useViewer;
|
||||
|
||||
Reference in New Issue
Block a user