Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: raycaster does not work properly when scene is not in full screen #304

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
efcfcbf
chore: tinkering on possible solutions concerning pointer event handling
Tinoooo Jun 1, 2023
bbd9fbe
chore: made click listeners work with changed architectures concernin…
Tinoooo Jun 1, 2023
06be907
chore: changed callback structure
Tinoooo Jun 13, 2023
62af8e0
chore: made pointer move work
Tinoooo Jun 13, 2023
f069d36
chore: made other pointer events work
Tinoooo Jun 13, 2023
c8d32bd
chore: code cleanup
Tinoooo Jun 13, 2023
d2cca14
chore: added deregistration of pointer event handlers for when an Oje…
Tinoooo Jun 14, 2023
ba9aedc
chore: handled the case when the pointer leaves an Object3D but also …
Tinoooo Jun 14, 2023
0625dda
Merge remote-tracking branch 'origin/main' into bugfix/282-raycaster-…
Tinoooo Jun 14, 2023
d2ef57a
chore: replaced useRaycaster
Tinoooo Jun 14, 2023
27a774e
Merge branch 'main' into bugfix/282-raycaster-does-not-work-properly-…
alvarosabu Jun 15, 2023
a740a8e
fix: raycaster works properly when scene does not take up the whole v…
Tinoooo Jun 16, 2023
11d5bff
chore: made types in nodeOps a little more specific
Tinoooo Jun 16, 2023
8a4c3f4
chore: improved click event handling
Tinoooo Jun 16, 2023
d540591
docs: adjusted events page
Tinoooo Jun 16, 2023
41a32b9
chore: fixed typo
Tinoooo Jun 16, 2023
51c3a70
chore: cleanup
Tinoooo Jun 16, 2023
de1bb54
chore: adjusted code so tests pass
Tinoooo Jun 16, 2023
ee80ee9
Merge branch 'main' into bugfix/282-raycaster-does-not-work-properly-…
Tinoooo Jun 16, 2023
80f6a5d
chore: merge latest main
alvarosabu Jun 19, 2023
7766fce
Merge remote-tracking branch 'origin/main' into bugfix/282-raycaster-…
alvarosabu Jun 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore: tinkering on possible solutions concerning pointer event handling
  • Loading branch information
Tinoooo committed Jun 1, 2023
commit efcfcbf301195c50f45752ad3336bf2c4e970556
2 changes: 1 addition & 1 deletion playground/src/components/TheEvents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function onPointerLeave(ev) {
<template v-for="x in [-2.5, 0, 2.5]">
<template v-for="y in [-2.5, 0, 2.5]">
<TresMesh
v-for="z in [-2.5, 0, 2.5]"
v-for="z in [-2.5, 0, 1.5]"
:key="[x, y, z]"
:position="[x, y, z]"
@click="onClick"
Expand Down
2 changes: 1 addition & 1 deletion playground/src/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts"></script>
<template>
<Suspense>
<TheExperience />
<TheEvents />
</Suspense>
</template>
40 changes: 4 additions & 36 deletions src/components/TresScene.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { App, defineComponent, h, onMounted, onUnmounted, ref, watch, watchEffect, VNode } from 'vue'
import { App, defineComponent, h, onMounted, onUnmounted, ref, watch, VNode } from 'vue'
import * as THREE from 'three'
import { ColorSpace, ShadowMapType, ToneMapping } from 'three'
import { useEventListener } from '@vueuse/core'
import { isString } from '@alvarosabu/utils'
import { createTres } from '../core/renderer'
import { TresCamera } from '../types/'
Expand All @@ -12,8 +11,8 @@ import {
useCamera,
useRenderer,
useRenderLoop,
useRaycaster,
useTres,
usePointerEventHandler,
} from '../composables'
import { extend } from '../core/catalogue'
import { type RendererPresetsType } from '../composables/useRenderer/const'
Expand Down Expand Up @@ -68,11 +67,13 @@ export const TresScene = defineComponent<TresSceneProps>({
const container = ref<HTMLElement>()
const canvas = ref<HTMLElement>()
const scene = new THREE.Scene()
const pointerEventHandler = usePointerEventHandler()
const { setState } = useTres()

setState('scene', scene)
setState('canvas', canvas)
setState('container', container)
setState('pointerEventHandler', pointerEventHandler)

const isCameraAvailable = ref()

Expand Down Expand Up @@ -105,41 +106,8 @@ export const TresScene = defineComponent<TresSceneProps>({
pushCamera(props.camera as any)
}

const { raycaster, pointer } = useRaycaster()

// TODO: Type raycasting events correctly
let prevInstance: any = null
let currentInstance: any = null

watchEffect(() => {
if (activeCamera.value) raycaster.value.setFromCamera(pointer.value, activeCamera.value)
})

onLoop(() => {
if (activeCamera.value && props.disableRender !== true) renderer.value?.render(scene, activeCamera.value)

if (raycaster.value) {
const intersects = raycaster.value.intersectObjects(scene.children)

if (intersects.length > 0) {
currentInstance = intersects[0]
if (prevInstance === null) {
currentInstance.object?.events?.onPointerEnter?.(currentInstance)
}
currentInstance.object?.events?.onPointerMove?.(currentInstance)
} else {
if (prevInstance !== null) {
currentInstance?.object?.events?.onPointerLeave?.(prevInstance)
currentInstance = null
}
}
prevInstance = currentInstance
}
})

useEventListener(canvas.value, 'click', () => {
if (currentInstance === null) return
currentInstance.object?.events?.onClick?.(currentInstance)
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export * from './useTres'
export * from './useRaycaster'
export * from './useLogger'
export * from './useSeek'
export * from './useRaycaster2'
export * from './usePointerEventHandler'
34 changes: 34 additions & 0 deletions src/composables/usePointerEventHandler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { computed } from 'vue'
import { useRaycaster2 } from '../useRaycaster2'
import type { Object3D } from 'three'

type EventProps = {
// TODO this should not be here but in the type that is used for mesh props
// TODO deep
onClick?: () => void
onPointerEnter?: () => void
onPointerMove?: () => void
onPointerLeave?: () => void
}

export const usePointerEventHandler = () => {
const objectsWithEventListeners = {
click: new Map<Object3D, () => void>(),
}

const registerObject = (object: Object3D, { onClick }: EventProps) => {
if (onClick) objectsWithEventListeners.click.set(object, onClick)
}

const objectsToWatch = computed(() => Array.from(objectsWithEventListeners.click.keys()))

const { onClick } = useRaycaster2(objectsToWatch)

onClick(({ intersects }) => {
console.log(intersects)
})

return {
registerObject,
}
}
99 changes: 99 additions & 0 deletions src/composables/useRaycaster2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useTres } from '../useTres'
import { Raycaster, Vector2 } from 'three'
import { Ref, computed, onUnmounted, ref, watchEffect } from 'vue'
import { createEventHook, useElementBounding, usePointer } from '@vueuse/core'

export type Intersects = THREE.Intersection<THREE.Object3D<THREE.Event>>[]
interface ClickEventPayload {
intersects: Intersects
event: PointerEvent
}

export const useRaycaster2 = (objects: Ref<THREE.Object3D[]>) => {
const { state } = useTres()

const canvas = computed(() => state.canvas?.value) // having a seperate computed makes useElementBounding work

const { x, y } = usePointer({ target: canvas }) // TODO advanced pointer options?

const { width, height, top, left } = useElementBounding(canvas)

const raycaster = new Raycaster()

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 (!state.camera) return

raycaster.setFromCamera(new Vector2(x, y), state.camera)

return raycaster.intersectObjects(objects.value, false)
}

const getIntersects = (event?: PointerEvent) => {
const pointerPosition = getRelativePointerPosition({
x: event?.clientX ?? x.value,
y: event?.clientY ?? y.value,
})
if (!pointerPosition) return []
console.log(pointerPosition)

return getIntersectsByRelativePointerPosition(pointerPosition) || []
}

// const intersects = ref<Intersects>([])

// watchEffect(() => {
// intersects.value = getIntersects()
// })

const eventHookClick = createEventHook<ClickEventPayload>()

const triggerEventHookClick = (event: PointerEvent) => {
eventHookClick.trigger({ event, intersects: getIntersects(event) })
}

//distinguishing between clicks and drags (in cas of panning or dollying for example)
let clicked = false

const onPointerDown = () => {
clicked = true
}
const onPointerMove = () => {
clicked = false
}
const onPointerUp = (event: PointerEvent) => {
if (!(event instanceof PointerEvent)) return // prevents triggering twice on mobile devices

if (clicked) triggerEventHookClick(event)
}

const unwatch = watchEffect(() => {
if (!canvas?.value) return

canvas.value.addEventListener('pointerdown', onPointerDown)
canvas.value.addEventListener('pointermove', onPointerMove)
canvas.value.addEventListener('pointerup', onPointerUp)

unwatch()
})

onUnmounted(() => {
if (!canvas?.value) return
canvas.value.removeEventListener('pointerdown', onPointerDown)
canvas.value.removeEventListener('pointermove', onPointerMove)
canvas.value.removeEventListener('pointerup', onPointerUp)
})

return {
// intersects,
onClick: (fn: (value: ClickEventPayload) => void) => eventHookClick.on(fn).off,
}
}
2 changes: 1 addition & 1 deletion src/composables/useSeek/useSeek.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { withSetup } from '../../utils/test-utils'

const [composable, app] = withSetup(() => useSeek())

describe('useRaycaster', () => {
describe('useSeek', () => {
afterEach(() => {
app.unmount()
})
Expand Down
12 changes: 10 additions & 2 deletions src/composables/useTres/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Clock, EventDispatcher, Raycaster, Scene, Vector2, WebGLRenderer } from 'three'
import { generateUUID } from 'three/src/math/MathUtils'
import { computed, ComputedRef, inject, provide, shallowReactive, toRefs } from 'vue'
import { computed, ComputedRef, inject, provide, Ref, shallowReactive, toRefs } from 'vue'
import { Camera } from '../useCamera'
import type { usePointerEventHandler } from '../usePointerEventHandler'

export interface TresState {
/**
Expand Down Expand Up @@ -88,6 +89,9 @@ export interface TresState {
* @memberof TresState
*/
controls?: (EventDispatcher & { enabled: boolean }) | null

canvas?: Ref<HTMLElement>
pointerEventHandler?: ReturnType<typeof usePointerEventHandler> // TODO comment
[key: string]: any
}

Expand All @@ -113,9 +117,11 @@ export function useTresProvider() {
uuid: generateUUID(),
camera: undefined,
cameras: [],
canvas: undefined,
scene: undefined,
renderer: undefined,
aspectRatio: computed(() => window.innerWidth / window.innerHeight),
aspectRatio: computed(() => window.innerWidth / window.innerHeight), //TODO
pointerEventHandler: undefined,
})
/**
* Get a state value.
Expand Down Expand Up @@ -155,8 +161,10 @@ export const useTres = () => {
state: shallowReactive({
camera: undefined,
cameras: [],
canvas: undefined,
scene: undefined,
renderer: undefined,
pointerEventHandler: undefined,
}),
})

Expand Down
22 changes: 19 additions & 3 deletions src/core/nodeOps.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { RendererOptions } from 'vue'
import { BufferAttribute, BufferGeometry, Material } from 'three'
import { useCamera, useLogger } from '../composables'
import { RendererOptions, getCurrentInstance } from 'vue'
import { BufferAttribute } from 'three'
import { useCamera, useLogger, useTres } from '../composables'
import { isFunction } from '@alvarosabu/utils'
import { catalogue } from './catalogue'
import { EventHandlers, TresObject } from '../types'
import { isHTMLTag, kebabToCamel } from '../utils'
import type { Object3D, Material, BufferGeometry } from 'three'

const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key)
Expand Down Expand Up @@ -155,6 +156,21 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
if (isOn(key)) {
const eventHandlerKey: keyof EventHandlers = key as keyof EventHandlers // This is fine
node.events[eventHandlerKey] = nextValue

if (getCurrentInstance()) {
// the following should only happen if a setup context is available
const ctx = useTres()

if (ctx && eventHandlerKey === 'onClick') {
console.log(ctx)

ctx.state.pointerEventHandler?.registerObject(node as Object3D, {
[eventHandlerKey]: nextValue,
})
}
}

// if (!ctx.state.pointerEventHandler) throw 'pointerEventHandler should be available'
}
let value = nextValue
if (value === '') value = true
Expand Down