-
-
Notifications
You must be signed in to change notification settings - Fork 134
/
Copy pathindex.ts
106 lines (80 loc) · 3.6 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { Vector2 } from 'three'
import type { Object3D, Intersection } from 'three'
import type { Ref } from 'vue'
import { computed, onUnmounted } from 'vue'
import type { EventHook } from '@vueuse/core'
import { createEventHook, useElementBounding, usePointer } from '@vueuse/core'
import { type TresContext } from '../useTresContextProvider'
export type Intersects = Intersection<THREE.Object3D<THREE.Object3DEventMap>>[]
interface PointerMoveEventPayload {
intersects?: Intersects
event: PointerEvent
}
interface PointerClickEventPayload {
intersects: Intersects
event: PointerEvent
}
export const useRaycaster = (
objects: Ref<THREE.Object3D[]>,
ctx: TresContext,
) => {
// having a separate computed makes useElementBounding work
const canvas = computed(() => ctx.renderer.value.domElement as HTMLCanvasElement)
const { x, y } = usePointer({ target: canvas })
const { width, height, top, left } = useElementBounding(canvas)
const getRelativePointerPosition = ({ x, y }: { x: number; y: number }) => {
if (!canvas.value) return
return {
x: ((x - left.value) / width.value) * 2 - 1,
y: -((y - top.value) / height.value) * 2 + 1,
}
}
const getIntersectsByRelativePointerPosition = ({ x, y }: { x: number; y: number }) => {
if (!ctx.camera.value) return
ctx.raycaster.value.setFromCamera(new Vector2(x, y), ctx.camera.value)
return ctx.raycaster.value.intersectObjects(objects.value, false)
}
const getIntersects = (event?: PointerEvent | MouseEvent) => {
const pointerPosition = getRelativePointerPosition({
x: event?.clientX ?? x.value,
y: event?.clientY ?? y.value,
})
if (!pointerPosition) return []
return getIntersectsByRelativePointerPosition(pointerPosition) || []
}
const intersects = computed<Intersects>(() => getIntersects())
const eventHookClick = createEventHook<PointerClickEventPayload>()
const eventHookPointerMove = createEventHook<PointerMoveEventPayload>()
const triggerEventHook = (eventHook: EventHook, event: PointerEvent | MouseEvent) => {
eventHook.trigger({ event, intersects: getIntersects(event) })
}
const onPointerMove = (event: PointerEvent) => {
triggerEventHook(eventHookPointerMove, event)
}
// a click event is fired whenever a pointerdown happened after pointerup on the same object
let mouseDownObject: Object3D | undefined = undefined
const onPointerDown = (event: PointerEvent) => {
mouseDownObject = getIntersects(event)[0]?.object
}
const onPointerUp = (event: MouseEvent) => {
if (!(event instanceof PointerEvent)) return // prevents triggering twice on mobile devices
if (mouseDownObject === getIntersects(event)[0]?.object) triggerEventHook(eventHookClick, event)
}
const onPointerLeave = (event: PointerEvent) => eventHookPointerMove.trigger({ event, intersects: [] })
canvas.value.addEventListener('pointerup', onPointerUp)
canvas.value.addEventListener('pointerdown', onPointerDown)
canvas.value.addEventListener('pointermove', onPointerMove)
canvas.value.addEventListener('pointerleave', onPointerLeave)
onUnmounted(() => {
if (!canvas?.value) return
canvas.value.removeEventListener('pointerup', onPointerUp)
canvas.value.removeEventListener('pointerdown', onPointerDown)
canvas.value.removeEventListener('pointermove', onPointerMove)
canvas.value.removeEventListener('pointerleave', onPointerLeave)
})
return {
intersects,
onClick: (fn: (value: PointerClickEventPayload) => void) => eventHookClick.on(fn).off,
onPointerMove: (fn: (value: PointerMoveEventPayload) => void) => eventHookPointerMove.on(fn).off,
}
}