Prechádzať zdrojové kódy

feat!(events): new event system based on pmdrs/pointer-events

BREAKING CHANGE: Only first element intersected wil trigger the pointer event, no more need to stop propagation on occlusion

* feat: get the context object tree with max 4 leves deep

* feat: enhance context node creation and graph building

- Updated `createContextNode` to include an optional `parentKey` parameter for better context chaining.
- Modified `buildContextGraph` to utilize the new `parentKey` for constructing chained keys during recursion.
- Added handling for context nodes in the inspector state, allowing for dynamic traversal of context objects based on chained keys.
- Improved readability and maintainability of the context graph logic.

* fix: improve scene object handling in Tres Devtools

- Enhanced the logic for extracting UUIDs from scene node IDs to ensure proper handling of scene objects.
- Updated the inspector state editing to reflect the new UUID extraction method, improving reliability when editing scene objects.
- Set the `editable` property to false for certain inspector values to prevent unintended modifications.

* fix: lint issue with fonts

* feat: implement inspector handlers for Tres Devtools

- Added `inspectorHandlers.ts` to manage inspector tree and state updates, enhancing the interaction with the Tres context.
- Introduced functions for creating nodes in the inspector tree, building graphs for scene and context objects, and handling state edits.
- Updated `TresCanvas.vue` to ensure proper context handling when registering Tres Devtools.
- Refactored `plugin.ts` to utilize the new inspector handlers, improving code organization and maintainability.
- Created type definitions in `types.ts` for better clarity and type safety in inspector-related functionalities.

* feat!(events): new event system based on pmdrs/pointer-events

BREAKING CHANGE: Only first element intersected wil trigger the pointer event, no more need to stop propagation on occlusion

* refactor: update event handler parameter naming for clarity

- Renamed the parameter in the onPointerMove function from 'ev' to '_ev' to indicate that it is intentionally unused, improving code readability.
- Removed unused import 'TresObject' from useTresContextProvider to clean up the codebase.

* refactor: update type imports and event handling in TresCanvas

- Replaced the Camera type with TresCamera in TresCanvasProps for better type specificity.
- Cleaned up imports in TresCanvas.vue by removing unused imports.
- Updated event handling in useEventManager to use pointerEventsMap for onClick, with a TODO for future type improvements.
- Modified handlers in LocalState to accept both PointerEventType and string for enhanced flexibility.
- Adjusted deregistration method in doRemoveDeregister to use context.events for consistency.

* refactor: update useTres composable to replace raycaster with events

- Modified the `useTres` composable to return `events` instead of `raycaster`, aligning with the updated context structure.
- Removed the `uuid` property from the `TresContext` interface and cleaned up unused imports in `useTresContextProvider`, enhancing code maintainability.

* refactor: update useLoop composable to replace raycaster with events

- Modified the `useLoop` composable to utilize `events` instead of `raycaster`, aligning with the recent changes in the Tres context structure.
- This change enhances the integration with the updated event system, ensuring better context handling during the rendering loop.

* wip

* separated event utils

* beautified types

* type fixes

* moved update call

* added todos

* omitted potential memory leak and callback calls of gone objects

* refactor: fixed onRender naming

* restored playground example

* Updated the `offPointerMissed` assignment to create a separate listener for each object, preventing unintended shared calls.

* improved comment

* renamed eventManager in context

* removed obsolete code

* chore: update @tresjs/cientos dependency to version 5.0.0-next.0 in package.json and playground/vue/package.json

* chore(playground): streamline event handling and remove unused Box component

- Removed the `stopPropagation` control logic from event handlers in `index.vue` to simplify the code.
- Updated event logging messages for consistency in `index.vue` and `groups/index.vue`.
- Enhanced the `TresGroup` component in `groups/index.vue` to include a new `@pointermissed` event handler.
- Deleted the unused `Box.vue` and `index.vue` files from the propagation directory to clean up the codebase.

* went back to "events"

* removed pointermissed from nodeops

* fixed emits of canvas compoennt

* cleaned up types

* tiny readability improvement

* eslint fix

---------

Co-authored-by: alvarosabu <alvaro.saburido@gmail.com>
Tino Koch 3 týždňov pred
rodič
commit
f2013783ea

+ 2 - 1
package.json

@@ -71,13 +71,14 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@alvarosabu/utils": "^3.2.0",
     "@alvarosabu/utils": "^3.2.0",
+    "@pmndrs/pointer-events": "^6.6.17",
     "@vue/devtools-api": "^7.7.2",
     "@vue/devtools-api": "^7.7.2",
     "@vueuse/core": "^12.5.0"
     "@vueuse/core": "^12.5.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@release-it/conventional-changelog": "^10.0.0",
     "@release-it/conventional-changelog": "^10.0.0",
     "@stackblitz/sdk": "^1.11.0",
     "@stackblitz/sdk": "^1.11.0",
-    "@tresjs/cientos": "https://pkg.pr.new/@tresjs/cientos@14afe95",
+    "@tresjs/cientos": "5.0.0-next.0",
     "@tresjs/eslint-config": "^1.4.0",
     "@tresjs/eslint-config": "^1.4.0",
     "@types/three": "^0.173.0",
     "@types/three": "^0.173.0",
     "@typescript-eslint/eslint-plugin": "^8.23.0",
     "@typescript-eslint/eslint-plugin": "^8.23.0",

+ 10 - 10
playground/vue/.eslintrc-auto-import.json

@@ -3,13 +3,18 @@
     "Component": true,
     "Component": true,
     "ComponentPublicInstance": true,
     "ComponentPublicInstance": true,
     "ComputedRef": true,
     "ComputedRef": true,
+    "DirectiveBinding": true,
     "EffectScope": true,
     "EffectScope": true,
     "ExtractDefaultPropTypes": true,
     "ExtractDefaultPropTypes": true,
     "ExtractPropTypes": true,
     "ExtractPropTypes": true,
     "ExtractPublicPropTypes": true,
     "ExtractPublicPropTypes": true,
     "InjectionKey": true,
     "InjectionKey": true,
+    "MaybeRef": true,
+    "MaybeRefOrGetter": true,
     "PropType": true,
     "PropType": true,
     "Ref": true,
     "Ref": true,
+    "Slot": true,
+    "Slots": true,
     "VNode": true,
     "VNode": true,
     "WritableComputedRef": true,
     "WritableComputedRef": true,
     "computed": true,
     "computed": true,
@@ -41,6 +46,7 @@
     "onServerPrefetch": true,
     "onServerPrefetch": true,
     "onUnmounted": true,
     "onUnmounted": true,
     "onUpdated": true,
     "onUpdated": true,
+    "onWatcherCleanup": true,
     "provide": true,
     "provide": true,
     "reactive": true,
     "reactive": true,
     "readonly": true,
     "readonly": true,
@@ -58,19 +64,13 @@
     "useAttrs": true,
     "useAttrs": true,
     "useCssModule": true,
     "useCssModule": true,
     "useCssVars": true,
     "useCssVars": true,
+    "useId": true,
+    "useModel": true,
     "useSlots": true,
     "useSlots": true,
+    "useTemplateRef": true,
     "watch": true,
     "watch": true,
     "watchEffect": true,
     "watchEffect": true,
     "watchPostEffect": true,
     "watchPostEffect": true,
-    "watchSyncEffect": true,
-    "DirectiveBinding": true,
-    "MaybeRef": true,
-    "MaybeRefOrGetter": true,
-    "onWatcherCleanup": true,
-    "useId": true,
-    "useModel": true,
-    "useTemplateRef": true,
-    "Slot": true,
-    "Slots": true
+    "watchSyncEffect": true
   }
   }
 }
 }

+ 1 - 1
playground/vue/package.json

@@ -9,7 +9,7 @@
     "preview": "vite preview"
     "preview": "vite preview"
   },
   },
   "dependencies": {
   "dependencies": {
-    "@tresjs/cientos": "https://pkg.pr.new/@tresjs/cientos@4fe342a",
+    "@tresjs/cientos": "5.0.0-next.0",
     "@tresjs/core": "workspace:^",
     "@tresjs/core": "workspace:^",
     "@tresjs/leches": "https://pkg.pr.new/@tresjs/leches@b34e795",
     "@tresjs/leches": "https://pkg.pr.new/@tresjs/leches@b34e795",
     "vue-router": "^4.5.0"
     "vue-router": "^4.5.0"

+ 7 - 3
playground/vue/src/pages/events/groups/index.vue

@@ -8,11 +8,15 @@ const handleClick = (e: PointerEvent) => {
 }
 }
 
 
 const handlePointerEnter = (e: PointerEvent) => {
 const handlePointerEnter = (e: PointerEvent) => {
-  console.log('pointer-enter', e)
+  console.log('pointerenter', e)
 }
 }
 
 
 const handlePointerLeave = (e: PointerEvent) => {
 const handlePointerLeave = (e: PointerEvent) => {
-  console.log('pointer-leave', e)
+  console.log('pointerleave', e)
+}
+
+const handlePointerMissed = (e: PointerEvent) => {
+  console.log('pointermissed', e)
 }
 }
 /* eslint-enable no-console */
 /* eslint-enable no-console */
 </script>
 </script>
@@ -31,7 +35,7 @@ const handlePointerLeave = (e: PointerEvent) => {
     <TresAmbientLight :intensity="0.5" />
     <TresAmbientLight :intensity="0.5" />
 
 
     <!-- Group of geometric shapes -->
     <!-- Group of geometric shapes -->
-    <TresGroup @click="handleClick" @pointer-enter="handlePointerEnter" @pointer-leave="handlePointerLeave">
+    <TresGroup @click="handleClick" @pointerenter="handlePointerEnter" @pointerleave="handlePointerLeave" @pointermissed="handlePointerMissed">
       <!-- Box -->
       <!-- Box -->
       <TresMesh :position="[-2, 0, 0]">
       <TresMesh :position="[-2, 0, 0]">
         <TresBoxGeometry :args="[1, 1, 1]" />
         <TresBoxGeometry :args="[1, 1, 1]" />

+ 28 - 40
playground/vue/src/pages/events/index.vue

@@ -1,9 +1,8 @@
 <!-- eslint-disable no-console -->
 <!-- eslint-disable no-console -->
 <script setup lang="ts">
 <script setup lang="ts">
-import type { ThreeEvent } from '@tresjs/core'
-import { OrbitControls, StatsGl } from '@tresjs/cientos'
-import { TresCanvas } from '@tresjs/core'
-import { TresLeches, useControls } from '@tresjs/leches'
+import { OrbitControls } from '@tresjs/cientos'
+import { TresCanvas, type TresPointerEvent } from '@tresjs/core'
+import { TresLeches } from '@tresjs/leches'
 import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
 import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
 import '@tresjs/leches/styles'
 import '@tresjs/leches/styles'
 
 
@@ -16,47 +15,38 @@ const gl = {
   toneMapping: NoToneMapping,
   toneMapping: NoToneMapping,
 }
 }
 
 
-useControls('fpsgraph')
-
-const { stopPropagation } = useControls({
-  stopPropagation: false,
-})
-
-function onClick(ev: ThreeEvent<MouseEvent>) {
+function onClick(ev: TresPointerEvent) {
   console.log('click', ev)
   console.log('click', ev)
-  if (stopPropagation.value) { ev.stopPropagation() }
   ev.object.material.color.set('#008080')
   ev.object.material.color.set('#008080')
 }
 }
 
 
-function onDoubleClick(ev: ThreeEvent<MouseEvent>) {
-  console.log('double-click', ev)
-  if (stopPropagation.value) { ev.stopPropagation() }
+function onDoubleClick(ev: TresPointerEvent) {
+  console.log('doubleclick', ev)
   ev.object.material.color.set('#FFD700')
   ev.object.material.color.set('#FFD700')
 }
 }
 
 
-function onPointerEnter(ev: ThreeEvent<MouseEvent>) {
-  if (stopPropagation.value) { ev.stopPropagation() }
+function onPointerEnter(ev: TresPointerEvent) {
+  console.log('pointerenter', ev)
   ev.object.material.color.set('#CCFF03')
   ev.object.material.color.set('#CCFF03')
 }
 }
 
 
-function onPointerLeave(ev: ThreeEvent<MouseEvent>) {
-  if (stopPropagation.value) { ev.stopPropagation() }
-  /*  ev.object.material.color.set('#efefef') */
+function onPointerLeave(ev: TresPointerEvent) {
+  console.log('pointerleave', ev)
+  ev.object.material.color.set('#efefef')
 }
 }
 
 
-function onPointerMove(ev: ThreeEvent<MouseEvent>) {
-  if (stopPropagation.value) { ev.stopPropagation() }
+// eslint-disable-next-line unused-imports/no-unused-vars
+function onPointerMove(ev: TresPointerEvent) {
+  // console.log('pointer-move', ev)
 }
 }
 
 
-function onContextMenu(ev: ThreeEvent<MouseEvent>) {
-  console.log('context-menu', ev)
-  if (stopPropagation.value) { ev.stopPropagation() }
+function onContextMenu(ev: TresPointerEvent) {
+  console.log('contextmenu', ev)
   ev.object.material.color.set('#FF4500')
   ev.object.material.color.set('#FF4500')
 }
 }
 
 
-function onPointerMissed(ev: ThreeEvent<MouseEvent>) {
-  console.log('pointer-missed', ev)
-  if (stopPropagation.value) { ev.stopPropagation() }
+function onPointerMissed(ev: TresPointerEvent) {
+  console.log('pointermissed', ev)
 }
 }
 </script>
 </script>
 
 
@@ -65,28 +55,26 @@ function onPointerMissed(ev: ThreeEvent<MouseEvent>) {
   <TresCanvas
   <TresCanvas
     window-size
     window-size
     v-bind="gl"
     v-bind="gl"
+    @pointermissed="onPointerMissed"
   >
   >
-    <Suspense>
-      <StatsGl />
-    </Suspense>
     <TresPerspectiveCamera
     <TresPerspectiveCamera
       :position="[11, 11, 11]"
       :position="[11, 11, 11]"
       :look-at="[0, 0, 0]"
       :look-at="[0, 0, 0]"
     />
     />
     <OrbitControls />
     <OrbitControls />
-    <template v-for="x in [-2.5, 0, 2.5]">
-      <template v-for="y in [-2.5, 0, 2.5]">
+    <template v-for="(x, xIndex) in [-2.5, 0, 2.5]">
+      <template v-for="(y, yIndex) in [-2.5, 0, 2.5]">
         <TresMesh
         <TresMesh
-          v-for="z in [-2.5, 0, 2.5]"
+          v-for="(z, zIndex) in [-2.5, 0, 2.5]"
           :key="`${[x, y, z]}`"
           :key="`${[x, y, z]}`"
           :position="[x, y, z]"
           :position="[x, y, z]"
+          :name="`box-${[xIndex, yIndex, zIndex].join('-')}`"
           @click="onClick"
           @click="onClick"
-          @double-click="onDoubleClick"
-          @pointer-enter="onPointerEnter"
-          @pointer-leave="onPointerLeave"
-          @pointer-move="onPointerMove"
-          @context-menu="onContextMenu"
-          @pointer-missed="onPointerMissed"
+          @dblclick="onDoubleClick"
+          @pointerenter="onPointerEnter"
+          @pointerleave="onPointerLeave"
+          @pointermove="onPointerMove"
+          @contextmenu="onContextMenu"
         >
         >
           <TresBoxGeometry :args="[1, 1, 1]" />
           <TresBoxGeometry :args="[1, 1, 1]" />
           <TresMeshToonMaterial color="#efefef" />
           <TresMeshToonMaterial color="#efefef" />

+ 0 - 44
playground/vue/src/pages/events/propagation/Box.vue

@@ -1,44 +0,0 @@
-<script setup lang="ts">
-import { useRenderLoop } from '@tresjs/core'
-import { Color } from 'three'
-import { ref, shallowRef } from 'vue'
-
-const props = defineProps(['position', 'name'])
-
-// TODO: Once we have troika text in cientos, display the count over each box
-const count = ref(0)
-const boxRef = shallowRef()
-
-// Event Testing Colors
-const black = new Color('black')
-const green = new Color('green')
-
-const blue = new Color('blue')
-
-// Once the box has flashed green, lerp it back to black
-const { onLoop } = useRenderLoop()
-onLoop(() => {
-  boxRef.value?.material.color.lerp(black, 0.1)
-})
-
-// onClick flash the box a color and update the counter
-function handleClick(color: Color, ev) {
-  count.value++
-  ev?.eventObject?.material.color.set(color)
-  // eslint-disable-next-line no-console
-  console.log(`Box ${boxRef.value.name} count=${count.value}`)
-}
-</script>
-
-<template>
-  <TresMesh
-    ref="boxRef"
-    v-bind="props"
-    @click.self="ev => handleClick(green, ev)"
-    @pointer-missed="ev => handleClick(blue, ev)"
-  >
-    <TresBoxGeometry />
-    <TresMeshStandardMaterial />
-    <slot></slot>
-  </TresMesh>
-</template>

+ 0 - 188
playground/vue/src/pages/events/propagation/index.vue

@@ -1,188 +0,0 @@
-<script setup lang="ts">
-import { OrbitControls } from '@tresjs/cientos'
-import {
-  TresCanvas,
-} from '@tresjs/core'
-import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
-import { onUnmounted, ref } from 'vue'
-import Box from './Box.vue'
-import '@tresjs/leches/styles'
-
-const gl = {
-  clearColor: '#202020',
-  shadows: true,
-  alpha: false,
-  shadowMapType: BasicShadowMap,
-  outputColorSpace: SRGBColorSpace,
-  toneMapping: NoToneMapping,
-}
-
-const showBox = ref(true)
-
-const intervalRef = setInterval(() => {
-  // showBox.value = !showBox.value;
-}, 1000)
-
-onUnmounted(() => {
-  clearInterval(intervalRef)
-})
-</script>
-
-<template>
-  <TresCanvas
-    window-size
-    v-bind="gl"
-    @pointer-missed="event => console.log('pointer-missed', event)"
-  >
-    <TresPerspectiveCamera
-      :position="[0, 0, 6]"
-      :look-at="[0, 0, 0]"
-    />
-    <OrbitControls />
-
-    <TresDirectionalLight
-      :intensity="1"
-      :position="[1, 1, 1]"
-    />
-    <TresAmbientLight :intensity="1" />
-    <Box
-      :position="[0, 1.5, 0]"
-      name="A0"
-    >
-      <Box
-        :position="[-0.66, -1, 0]"
-        name="B0"
-      >
-        <Box
-          :position="[-0.66, -1, 0]"
-          name="C0"
-        >
-          <Box
-            :position="[-0.66, -1, 0]"
-            name="D0"
-          />
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D1"
-          />
-        </Box>
-        <Box
-          :position="[0.66, -1, 0]"
-          name="C1"
-        >
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D2"
-          />
-        </Box>
-      </Box>
-      <Box
-        :position="[0.66, -1, 0]"
-        name="B1"
-      >
-        <Box
-          :position="[0.66, -1, 0]"
-          name="C2"
-        >
-          <Box
-            v-if="showBox"
-            :position="[0.66, -1, 0]"
-            name="D3"
-          />
-        </Box>
-      </Box>
-    </Box>
-    <Box
-      :position="[0, 1.5, -3]"
-      name="A0"
-    >
-      <Box
-        :position="[-0.66, -1, 0]"
-        name="B0"
-      >
-        <Box
-          :position="[-0.66, -1, 0]"
-          name="C0"
-        >
-          <Box
-            :position="[-0.66, -1, 0]"
-            name="D0"
-          />
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D1"
-          />
-        </Box>
-        <Box
-          :position="[0.66, -1, 0]"
-          name="C1"
-        >
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D2"
-          />
-        </Box>
-      </Box>
-      <Box
-        :position="[0.66, -1, 0]"
-        name="B1"
-      >
-        <Box
-          :position="[0.66, -1, 0]"
-          name="C2"
-        >
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D3"
-          />
-        </Box>
-      </Box>
-    </Box>
-    <Box
-      :position="[0, 1.5, -6]"
-      name="A0"
-    >
-      <Box
-        :position="[-0.66, -1, 0]"
-        name="B0"
-      >
-        <Box
-          :position="[-0.66, -1, 0]"
-          name="C0"
-        >
-          <Box
-            :position="[-0.66, -1, 0]"
-            name="D0"
-          />
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D1"
-          />
-        </Box>
-        <Box
-          :position="[0.66, -1, 0]"
-          name="C1"
-        >
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D2"
-          />
-        </Box>
-      </Box>
-      <Box
-        :position="[0.66, -1, 0]"
-        name="B1"
-      >
-        <Box
-          :position="[0.66, -1, 0]"
-          name="C2"
-        >
-          <Box
-            :position="[0.66, -1, 0]"
-            name="D3"
-          />
-        </Box>
-      </Box>
-    </Box>
-  </TresCanvas>
-</template>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 213 - 354
pnpm-lock.yaml


+ 14 - 24
src/components/TresCanvas.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import type {
 import type {
-  Camera,
   ColorSpace,
   ColorSpace,
   ShadowMapType,
   ShadowMapType,
   ToneMapping,
   ToneMapping,
@@ -8,8 +7,9 @@ import type {
   WebGLRendererParameters,
   WebGLRendererParameters,
 } from 'three'
 } from 'three'
 import type { App, Ref } from 'vue'
 import type { App, Ref } from 'vue'
+import type { PointerEvent } from '@pmndrs/pointer-events'
 import type { RendererPresetsType } from '../composables/useRenderer/const'
 import type { RendererPresetsType } from '../composables/useRenderer/const'
-import type { TresObject, TresPointerEvent, TresScene } from '../types/'
+import type { TresCamera, TresObject, TresScene } from '../types/'
 import { PerspectiveCamera, Scene } from 'three'
 import { PerspectiveCamera, Scene } from 'three'
 import * as THREE from 'three'
 import * as THREE from 'three'
 
 
@@ -36,9 +36,10 @@ import {
 import { extend } from '../core/catalogue'
 import { extend } from '../core/catalogue'
 import { nodeOps } from '../core/nodeOps'
 import { nodeOps } from '../core/nodeOps'
 
 
-import { disposeObject3D, kebabToCamel } from '../utils/'
+import { disposeObject3D } from '../utils/'
 import { registerTresDevtools } from '../devtools'
 import { registerTresDevtools } from '../devtools'
 import { whenever } from '@vueuse/core'
 import { whenever } from '@vueuse/core'
+import type { TresPointerEventName } from '../utils/pointerEvents'
 
 
 export interface TresCanvasProps
 export interface TresCanvasProps
   extends Omit<WebGLRendererParameters, 'canvas'> {
   extends Omit<WebGLRendererParameters, 'canvas'> {
@@ -54,7 +55,7 @@ export interface TresCanvasProps
   dpr?: number | [number, number]
   dpr?: number | [number, number]
 
 
   // required by useTresContextProvider
   // required by useTresContextProvider
-  camera?: Camera
+  camera?: TresCamera
   preset?: RendererPresetsType
   preset?: RendererPresetsType
   windowSize?: boolean
   windowSize?: boolean
 
 
@@ -81,18 +82,10 @@ const emit = defineEmits<{
   ready: [context: TresContext]
   ready: [context: TresContext]
   render: [renderer: WebGLRenderer]
   render: [renderer: WebGLRenderer]
 
 
-  click: [event: TresPointerEvent]
-  doubleClick: [event: TresPointerEvent]
-  contextMenu: [event: TresPointerEvent]
-  pointerMove: [event: TresPointerEvent]
-  pointerUp: [event: TresPointerEvent]
-  pointerDown: [event: TresPointerEvent]
-  pointerEnter: [event: TresPointerEvent]
-  pointerLeave: [event: TresPointerEvent]
-  pointerOver: [event: TresPointerEvent]
-  pointerOut: [event: TresPointerEvent]
-  pointerMissed: [event: TresPointerEvent]
-  wheel: [event: TresPointerEvent]
+  pointermissed: [event: PointerEvent<MouseEvent>]
+} & {
+  // all pointer events are supported because they bubble up
+  [key in TresPointerEventName]: [event: PointerEvent<MouseEvent>]
 }>()
 }>()
 
 
 const slots = defineSlots<{
 const slots = defineSlots<{
@@ -219,6 +212,10 @@ onMounted(() => {
     })
     })
   }
   }
 
 
+  context.value.events.onPointerMissed((event) => {
+    emit('pointermissed', event)
+  })
+
   watch(
   watch(
     () => props.camera,
     () => props.camera,
     (newCamera, oldCamera) => {
     (newCamera, oldCamera) => {
@@ -239,17 +236,10 @@ onMounted(() => {
     addDefaultCamera()
     addDefaultCamera()
   }
   }
 
 
-  renderer.onRender.on((renderer) => {
+  renderer.onRender((renderer) => {
     emit('render', renderer)
     emit('render', renderer)
   })
   })
 
 
-  context.value.eventManager?.onEvent(({ type, event, intersection }) => {
-    emit(
-      kebabToCamel(type) as any, // typescript doesn't know that kebabToCamel(type) is a valid key of PointerEmits
-      { type, event, intersection },
-    )
-  })
-
   // HMR support
   // HMR support
   if (import.meta.hot && context.value) { import.meta.hot.on('vite:afterUpdate', () => handleHMR(context.value as TresContext)) }
   if (import.meta.hot && context.value) { import.meta.hot.on('vite:afterUpdate', () => handleHMR(context.value as TresContext)) }
 })
 })

+ 1 - 2
src/composables/index.ts

@@ -4,11 +4,10 @@ export * from './useCamera'
 export * from './useGraph'
 export * from './useGraph'
 export * from './useLoader'
 export * from './useLoader'
 export * from './useLoop'
 export * from './useLoop'
-export * from './useRaycaster'
 export * from './useRenderer/useRendererManager'
 export * from './useRenderer/useRendererManager'
 export * from './useRenderLoop'
 export * from './useRenderLoop'
 export * from './useTres'
 export * from './useTres'
 
 
 export * from './useTresContextProvider'
 export * from './useTresContextProvider'
-export * from './useTresEventManager'
+
 export { UseLoader }
 export { UseLoader }

+ 13 - 13
src/composables/useCamera/index.ts

@@ -3,34 +3,34 @@ import type { TresContext } from '../useTresContextProvider'
 import type { ComputedRef, Ref } from 'vue'
 import type { ComputedRef, Ref } from 'vue'
 import { computed, ref, watchEffect } from 'vue'
 import { computed, ref, watchEffect } from 'vue'
 import { isCamera, isPerspectiveCamera } from '../../utils/is'
 import { isCamera, isPerspectiveCamera } from '../../utils/is'
-import type { Camera } from 'three'
+import type { TresCamera } from '../../types'
 
 
 /**
 /**
  * Interface for the return value of the useCamera composable
  * Interface for the return value of the useCamera composable
  */
  */
 export interface UseCameraReturn {
 export interface UseCameraReturn {
 
 
-  activeCamera: ComputedRef<Camera | undefined>
+  activeCamera: ComputedRef<TresCamera>
   /**
   /**
    * The list of cameras
    * The list of cameras
    */
    */
-  cameras: Ref<Camera[]>
+  cameras: Ref<TresCamera[]>
   /**
   /**
    * Register a camera
    * Register a camera
    * @param camera - The camera to register
    * @param camera - The camera to register
    * @param active - Whether to set the camera as active
    * @param active - Whether to set the camera as active
    */
    */
-  registerCamera: (camera: Camera, active?: boolean) => void
+  registerCamera: (camera: TresCamera, active?: boolean) => void
   /**
   /**
    * Deregister a camera
    * Deregister a camera
    * @param camera - The camera to deregister
    * @param camera - The camera to deregister
    */
    */
-  deregisterCamera: (camera: Camera) => void
+  deregisterCamera: (camera: TresCamera) => void
   /**
   /**
    * Set the active camera
    * Set the active camera
    * @param cameraOrUuid - The camera or its UUID to set as active
    * @param cameraOrUuid - The camera or its UUID to set as active
    */
    */
-  setActiveCamera: (cameraOrUuid: string | Camera) => void
+  setActiveCamera: (cameraOrUuid: string | TresCamera) => void
 }
 }
 
 
 /**
 /**
@@ -47,17 +47,17 @@ interface UseCameraParams {
  * @returns The camera management functions and state
  * @returns The camera management functions and state
  */
  */
 export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn => {
 export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn => {
-  const cameras = ref<Camera[]>([])
-  const activeCamera = computed<Camera | undefined>(() => cameras.value[0]) // the first camera is used to make sure there is always one camera active
+  const cameras = ref<TresCamera[]>([])
+  const activeCamera = computed<TresCamera>(() => cameras.value[0]) // the first camera is used to make sure there is always one camera active
 
 
   /**
   /**
    * Set the active camera
    * Set the active camera
    * @param cameraOrUuid - The camera or its UUID to set as active
    * @param cameraOrUuid - The camera or its UUID to set as active
    */
    */
-  const setActiveCamera = (cameraOrUuid: string | Camera) => {
+  const setActiveCamera = (cameraOrUuid: string | TresCamera) => {
     const camera = isCamera(cameraOrUuid)
     const camera = isCamera(cameraOrUuid)
       ? cameraOrUuid
       ? cameraOrUuid
-      : cameras.value.find((camera: Camera) => camera.uuid === cameraOrUuid)
+      : cameras.value.find((camera: TresCamera) => camera.uuid === cameraOrUuid)
 
 
     if (!camera) { return }
     if (!camera) { return }
 
 
@@ -70,7 +70,7 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    * @param camera - The camera to register
    * @param camera - The camera to register
    * @param active - Whether to set the camera as active
    * @param active - Whether to set the camera as active
    */
    */
-  const registerCamera = (camera: Camera, active = false): void => {
+  const registerCamera = (camera: TresCamera, active = false): void => {
     if (cameras.value.some(({ uuid }) => uuid === camera.uuid)) { return }
     if (cameras.value.some(({ uuid }) => uuid === camera.uuid)) { return }
     cameras.value.push(camera)
     cameras.value.push(camera)
 
 
@@ -83,7 +83,7 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    * Deregister a camera
    * Deregister a camera
    * @param camera - The camera to deregister
    * @param camera - The camera to deregister
    */
    */
-  const deregisterCamera = (camera: Camera): void => {
+  const deregisterCamera = (camera: TresCamera): void => {
     cameras.value = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
     cameras.value = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
   }
   }
 
 
@@ -92,7 +92,7 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    */
    */
   watchEffect(() => {
   watchEffect(() => {
     if (sizes.aspectRatio.value) {
     if (sizes.aspectRatio.value) {
-      cameras.value.forEach((camera: Camera) => {
+      cameras.value.forEach((camera: TresCamera) => {
         if (isPerspectiveCamera(camera)) {
         if (isPerspectiveCamera(camera)) {
           camera.aspect = sizes.aspectRatio.value
           camera.aspect = sizes.aspectRatio.value
           camera.updateProjectionMatrix()
           camera.updateProjectionMatrix()

+ 31 - 0
src/composables/useEventManager/index.ts

@@ -0,0 +1,31 @@
+import type { PointerEvent, PointerEventsMap } from '@pmndrs/pointer-events'
+import type { Event, Object3D, Object3DEventMap } from 'three'
+import { forwardHtmlEvents, getVoidObject } from '@pmndrs/pointer-events'
+import { onUnmounted, toValue } from 'vue'
+import type { MaybeRef } from 'vue'
+import type { TresContext } from '../useTresContextProvider'
+import { createEventHook } from '@vueuse/core'
+
+export function useEventManager({
+  canvas,
+  contextParts: { scene, camera, loop },
+}: {
+  canvas: MaybeRef<HTMLCanvasElement>
+  contextParts: Pick<TresContext, 'scene' | 'camera' | 'loop' >
+}) {
+  const { update, destroy } = forwardHtmlEvents(toValue(canvas), () => toValue(camera.activeCamera), scene.value)
+  const { off } = loop.register(update, 'before')
+  onUnmounted(destroy)
+  onUnmounted(off)
+
+  type VoidObject = Object3D<Object3DEventMap & PointerEventsMap>
+
+  const voidObject = getVoidObject(scene.value) as VoidObject
+  const pointerMissedEventHook = createEventHook<PointerEvent<MouseEvent> & Event<'click', VoidObject>>()
+
+  voidObject.addEventListener('click', pointerMissedEventHook.trigger)
+
+  return {
+    onPointerMissed: pointerMissedEventHook.on,
+  }
+}

+ 2 - 2
src/composables/useLoop/index.ts

@@ -7,8 +7,8 @@ export function useLoop() {
     scene,
     scene,
     renderer,
     renderer,
     loop,
     loop,
-    raycaster,
     controls,
     controls,
+    events,
   } = useTresContext()
   } = useTresContext()
 
 
   // Pass context to loop
   // Pass context to loop
@@ -16,8 +16,8 @@ export function useLoop() {
     camera,
     camera,
     scene,
     scene,
     renderer: renderer.instance,
     renderer: renderer.instance,
-    raycaster,
     controls,
     controls,
+    events,
   })
   })
 
 
   function onBeforeRender(cb: LoopCallbackFn, index = 0) {
   function onBeforeRender(cb: LoopCallbackFn, index = 0) {

+ 0 - 212
src/composables/useRaycaster/index.ts

@@ -1,212 +0,0 @@
-import { createEventHook, useElementBounding, usePointer } from '@vueuse/core'
-import { Vector2, Vector3 } from 'three'
-import { computed, onUnmounted, shallowRef } from 'vue'
-import type { EventHook } from '@vueuse/core'
-import type { Intersection, Object3D, Object3DEventMap } from 'three'
-import type { ShallowRef } from 'vue'
-import type { DomEvent, TresEvent, TresInstance } from '../../types'
-import type { TresContext } from '../useTresContextProvider'
-
-export const useRaycaster = (
-  objectsWithEvents: ShallowRef<TresInstance[]>,
-  ctx: TresContext,
-) => {
-  // having a separate computed makes useElementBounding work
-  const canvas = computed(() => ctx.renderer.instance.value.domElement as HTMLCanvasElement)
-  const intersects: ShallowRef<Intersection[]> = shallowRef([])
-  const { x, y } = usePointer({ target: canvas })
-  let delta = 0
-
-  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.activeCamera.value) { return }
-
-    ctx.raycaster.value.setFromCamera(new Vector2(x, y), ctx.camera.activeCamera.value)
-
-    intersects.value = ctx.raycaster.value.intersectObjects(objectsWithEvents.value as Object3D<Object3DEventMap>[], true)
-    return intersects.value
-  }
-
-  const getIntersects = (event?: DomEvent) => {
-    const pointerPosition = getRelativePointerPosition({
-      x: event?.clientX ?? x.value,
-      y: event?.clientY ?? y.value,
-    })
-    if (!pointerPosition) { return [] }
-
-    return getIntersectsByRelativePointerPosition(pointerPosition) || []
-  }
-
-  const eventHookClick = createEventHook<TresEvent>()
-  const eventHookDblClick = createEventHook<TresEvent>()
-  const eventHookPointerMove = createEventHook<TresEvent>()
-  const eventHookPointerUp = createEventHook<TresEvent>()
-  const eventHookPointerDown = createEventHook<TresEvent>()
-  const eventHookPointerMissed = createEventHook<TresEvent>()
-  const eventHookContextMenu = createEventHook<TresEvent>()
-  const eventHookWheel = createEventHook<TresEvent>()
-
-  /* ({
-    ...DomEvent                   // All the original event data
-    ...Intersection               // All of Three's intersection data - see note 2
-    intersections: Intersection[] // The first intersection of each intersected object
-    object: Object3D              // The object that was actually hit (added to event payload in TresEventManager)
-    eventObject: Object3D         // The object that registered the event (added to event payload in TresEventManager)
-    unprojectedPoint: Vector3     // Camera-unprojected point
-    ray: Ray                      // The ray that was used to strike the object
-    camera: Camera                // The camera that was used in the raycaster
-    sourceEvent: DomEvent         // A reference to the host event
-    delta: number                 // Distance between mouse down and mouse up event in pixels
-  }) => ... */
-
-  // Mouse Event props aren't enumerable, so we can't be simple and use Object.assign or the spread operator
-  // Manually copies the mouse event props into a new object that we can spread in triggerEventHook
-  function copyMouseEventProperties(event: MouseEvent | PointerEvent | WheelEvent) {
-    const mouseEventProperties: any = {}
-
-    for (const property in event) {
-      // Copy all non-function properties
-      if (typeof property !== 'function') { mouseEventProperties[property] = (event as Record<string, any>)[property] }
-    }
-    return mouseEventProperties
-  }
-
-  const triggerEventHook = (eventHook: EventHook, event: PointerEvent | MouseEvent | WheelEvent) => {
-    const eventProperties = copyMouseEventProperties(event)
-    if (!ctx.camera.activeCamera.value) { return }
-    const unprojectedPoint = new Vector3(event?.clientX, event?.clientY, 0).unproject(ctx.camera.activeCamera.value)
-    eventHook.trigger({
-      ...eventProperties,
-      intersections: intersects.value,
-      // The unprojectedPoint is wrong, math needs to be fixed
-      unprojectedPoint,
-      ray: ctx.raycaster?.value.ray,
-      camera: ctx.camera.activeCamera.value,
-      sourceEvent: event,
-      delta,
-      stopPropagating: false,
-    })
-  }
-
-  let previousPointerMoveEvent: PointerEvent | undefined
-  const onPointerMove = (event: PointerEvent) => {
-    // Update the raycast intersects
-    getIntersects(event)
-    triggerEventHook(eventHookPointerMove, event)
-    previousPointerMoveEvent = event
-  }
-
-  const forceUpdate = () => {
-    if (previousPointerMoveEvent) { onPointerMove(previousPointerMoveEvent) }
-  }
-
-  // a click event is fired whenever a pointerdown happened after pointerup on the same object
-  let mouseDownObject: Object3D | undefined
-  let mouseDownPosition: Vector2
-  let mouseUpPosition: Vector2
-
-  const onPointerDown = (event: PointerEvent) => {
-    mouseDownObject = intersects.value[0]?.object
-
-    delta = 0
-    mouseDownPosition = new Vector2(
-      event?.clientX ?? x.value,
-      event?.clientY ?? y.value,
-    )
-
-    triggerEventHook(eventHookPointerDown, event)
-  }
-
-  let previousClickObject: Object3D | undefined
-  let doubleClickConfirmed: boolean = false
-
-  const onPointerUp = (event: MouseEvent) => {
-    if (!(event instanceof PointerEvent)) { return } // prevents triggering twice on mobile devices
-
-    // We missed every object, trigger the pointer missed event
-    if (intersects.value.length === 0) {
-      triggerEventHook(eventHookPointerMissed, event)
-    }
-
-    if (mouseDownObject === intersects.value[0]?.object) {
-      mouseUpPosition = new Vector2(
-        event?.clientX ?? x.value,
-        event?.clientY ?? y.value,
-      )
-
-      // Compute the distance between the mouse down and mouse up events
-      delta = mouseDownPosition?.distanceTo(mouseUpPosition)
-
-      if (event.button === 0) {
-        // Left click
-        triggerEventHook(eventHookClick, event)
-
-        if (previousClickObject === intersects.value[0]?.object) {
-          doubleClickConfirmed = true
-        }
-        else {
-          previousClickObject = intersects.value[0]?.object
-          doubleClickConfirmed = false
-        }
-      }
-      else if (event.button === 2) {
-        // Right click
-        triggerEventHook(eventHookContextMenu, event)
-      }
-    }
-
-    triggerEventHook(eventHookPointerUp, event)
-  }
-
-  const onDoubleClick = (event: MouseEvent) => {
-    if (doubleClickConfirmed) {
-      triggerEventHook(eventHookDblClick, event)
-      previousClickObject = undefined
-      doubleClickConfirmed = false
-    }
-  }
-
-  const onPointerLeave = (event: PointerEvent) => triggerEventHook(eventHookPointerMove, event)
-
-  const onWheel = (event: WheelEvent) => triggerEventHook(eventHookWheel, event)
-
-  canvas.value.addEventListener('pointerup', onPointerUp)
-  canvas.value.addEventListener('pointerdown', onPointerDown)
-  canvas.value.addEventListener('pointermove', onPointerMove)
-  canvas.value.addEventListener('pointerleave', onPointerLeave)
-  canvas.value.addEventListener('dblclick', onDoubleClick)
-  canvas.value.addEventListener('wheel', onWheel)
-
-  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)
-    canvas.value.removeEventListener('dblclick', onDoubleClick)
-    canvas.value.removeEventListener('wheel', onWheel)
-  })
-
-  return {
-    intersects,
-    onClick: (fn: (value: TresEvent) => void) => eventHookClick.on(fn).off,
-    onDblClick: (fn: (value: TresEvent) => void) => eventHookDblClick.on(fn).off,
-    onContextMenu: (fn: (value: TresEvent) => void) => eventHookContextMenu.on(fn).off,
-    onPointerMove: (fn: (value: TresEvent) => void) => eventHookPointerMove.on(fn).off,
-    onPointerUp: (fn: (value: TresEvent) => void) => eventHookPointerUp.on(fn).off,
-    onPointerDown: (fn: (value: TresEvent) => void) => eventHookPointerDown.on(fn).off,
-    onPointerMissed: (fn: (value: TresEvent) => void) => eventHookPointerMissed.on(fn).off,
-    onWheel: (fn: (value: TresEvent) => void) => eventHookWheel.on(fn).off,
-    forceUpdate,
-  }
-}

+ 3 - 3
src/composables/useRenderer/useRendererManager.ts

@@ -171,13 +171,13 @@ export function useRendererManager(
 
 
   const isModeAlways = computed(() => toValue(options.renderMode) === 'always')
   const isModeAlways = computed(() => toValue(options.renderMode) === 'always')
 
 
-  const onRender = createEventHook<WebGLRenderer>()
+  const renderEventHook = createEventHook<WebGLRenderer>()
 
 
   loop.register(() => {
   loop.register(() => {
     if (camera.activeCamera.value && amountOfFramesToRender.value) {
     if (camera.activeCamera.value && amountOfFramesToRender.value) {
       instance.value.render(scene, camera.activeCamera.value)
       instance.value.render(scene, camera.activeCamera.value)
 
 
-      onRender.trigger(instance.value)
+      renderEventHook.trigger(instance.value)
     }
     }
 
 
     amountOfFramesToRender.value = isModeAlways.value
     amountOfFramesToRender.value = isModeAlways.value
@@ -310,7 +310,7 @@ export function useRendererManager(
     instance,
     instance,
     isReady: readonly(isReady),
     isReady: readonly(isReady),
     advance,
     advance,
-    onRender,
+    onRender: renderEventHook.on,
     invalidate,
     invalidate,
     canBeInvalidated,
     canBeInvalidated,
     amountOfFramesToRender,
     amountOfFramesToRender,

+ 2 - 2
src/composables/useTres/index.ts

@@ -37,7 +37,7 @@ export interface TresPartialContext extends Omit<TresContext, 'renderer' | 'came
 }
 }
 
 
 export function useTres(): TresPartialContext {
 export function useTres(): TresPartialContext {
-  const { scene, renderer, camera, sizes, controls, loop, extend, raycaster } = useTresContext()
+  const { scene, renderer, camera, sizes, controls, loop, extend, events } = useTresContext()
 
 
   return {
   return {
     scene,
     scene,
@@ -47,7 +47,7 @@ export function useTres(): TresPartialContext {
     controls,
     controls,
     loop,
     loop,
     extend,
     extend,
-    raycaster,
+    events,
     invalidate: () => renderer.invalidate(),
     invalidate: () => renderer.invalidate(),
     advance: () => renderer.advance(),
     advance: () => renderer.advance(),
   }
   }

+ 9 - 16
src/composables/useTresContextProvider/index.ts

@@ -1,9 +1,8 @@
-import { Raycaster } from 'three'
 import type { MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
 import type { MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
 import { whenever } from '@vueuse/core'
 import { whenever } from '@vueuse/core'
 
 
 import type { RendererLoop } from '../../core/loop'
 import type { RendererLoop } from '../../core/loop'
-import type { TresControl, TresObject, TresScene } from '../../types'
+import type { TresControl, TresScene } from '../../types'
 import type { UseRendererManagerReturn, UseRendererOptions } from '../useRenderer/useRendererManager'
 import type { UseRendererManagerReturn, UseRendererOptions } from '../useRenderer/useRendererManager'
 import { inject, onUnmounted, provide, ref, shallowRef } from 'vue'
 import { inject, onUnmounted, provide, ref, shallowRef } from 'vue'
 import { extend } from '../../core/catalogue'
 import { extend } from '../../core/catalogue'
@@ -14,7 +13,7 @@ import type { UseCameraReturn } from '../useCamera/'
 import { useCameraManager } from '../useCamera'
 import { useCameraManager } from '../useCamera'
 import { useRendererManager } from '../useRenderer/useRendererManager'
 import { useRendererManager } from '../useRenderer/useRendererManager'
 import useSizes, { type SizesType } from '../useSizes'
 import useSizes, { type SizesType } from '../useSizes'
-import { type TresEventManager, useTresEventManager } from '../useTresEventManager'
+import { useEventManager } from '../useEventManager'
 
 
 export interface TresContext {
 export interface TresContext {
   scene: ShallowRef<TresScene>
   scene: ShallowRef<TresScene>
@@ -23,17 +22,8 @@ export interface TresContext {
   camera: UseCameraReturn
   camera: UseCameraReturn
   controls: Ref<TresControl | null>
   controls: Ref<TresControl | null>
   renderer: UseRendererManagerReturn
   renderer: UseRendererManagerReturn
-  raycaster: ShallowRef<Raycaster>
-  // Loop
   loop: RendererLoop
   loop: RendererLoop
-  eventManager?: TresEventManager
-  // Events
-  // Temporaly add the methods to the context, this should be handled later by the EventManager state on the context https://github.com/Tresjs/tres/issues/515
-  // When thats done maybe we can short the names of the methods since the parent will give the context.
-  registerObjectAtPointerEventHandler?: (object: TresObject) => void
-  deregisterObjectAtPointerEventHandler?: (object: TresObject) => void
-  registerBlockingObjectAtPointerEventHandler?: (object: TresObject) => void
-  deregisterBlockingObjectAtPointerEventHandler?: (object: TresObject) => void
+  events: ReturnType<typeof useEventManager>
 }
 }
 
 
 export function useTresContextProvider({
 export function useTresContextProvider({
@@ -63,15 +53,20 @@ export function useTresContextProvider({
     },
     },
   )
   )
 
 
+  const events = useEventManager({
+    canvas,
+    contextParts: { scene: localScene, camera, loop },
+  })
+
   const ctx: TresContext = {
   const ctx: TresContext = {
     sizes,
     sizes,
     scene: localScene,
     scene: localScene,
     camera,
     camera,
     renderer,
     renderer,
-    raycaster: shallowRef(new Raycaster()),
     controls: ref(null),
     controls: ref(null),
     extend,
     extend,
     loop,
     loop,
+    events,
   }
   }
 
 
   provide('useTres', ctx)
   provide('useTres', ctx)
@@ -91,8 +86,6 @@ export function useTresContextProvider({
     immediate: true,
     immediate: true,
   })
   })
 
 
-  useTresEventManager(scene, ctx)
-
   onUnmounted(() => {
   onUnmounted(() => {
     ctx.loop.stop()
     ctx.loop.stop()
   })
   })

+ 0 - 237
src/composables/useTresEventManager/index.ts

@@ -1,237 +0,0 @@
-import type { Intersection, PointerEventType, TresEvent, TresInstance, TresObject, TresPointerEvent } from 'src/types'
-import type { Object3D, Object3DEventMap, Scene } from 'three'
-import type { TresContext } from '../useTresContextProvider'
-import { shallowRef } from 'vue'
-import { hyphenate } from '../../utils'
-import { useRaycaster } from '../useRaycaster'
-import { isObject3D, isTresObject } from '../../utils/is'
-import type { EventHookOff } from '@vueuse/core'
-import { createEventHook } from '@vueuse/core'
-
-export interface TresEventManager {
-  onEvent: EventHookOff<TresPointerEvent>
-  /**
-   * Forces the event system to refire events with the previous mouse event
-   */
-  forceUpdate: () => void
-  /**
-   * pointer-missed events by definition are fired when the pointer missed every object in the scene
-   * So we need to track them separately
-   * Note: These are used in nodeOps
-   */
-  registerObject: (object: unknown) => void
-  deregisterObject: (object: unknown) => void
-  registerPointerMissedObject: (object: unknown) => void
-  deregisterPointerMissedObject: (object: unknown) => void
-}
-function executeEventListeners(
-  listeners: (event: TresEvent) => void | ((event: TresEvent) => void)[],
-  event: TresEvent,
-) {
-  // Components with multiple event listeners will have an array of functions
-  if (Array.isArray(listeners)) {
-    for (const listener of listeners) {
-      listener(event)
-    }
-  }
-
-  // Single listener will be a function
-  if (typeof listeners === 'function') {
-    listeners(event)
-  }
-}
-
-export function useTresEventManager(
-  scene: Scene,
-  context: TresContext,
-) {
-  const _scene = shallowRef<Scene>()
-  const _context = shallowRef<TresContext>()
-
-  if (scene) { _scene.value = scene }
-  if (context) { _context.value = context }
-
-  const hasEvents = (object: TresInstance) => object.__tres?.eventCount > 0
-  const hasChildrenWithEvents = (object: TresInstance) => object.children?.some((child: TresInstance) => hasChildrenWithEvents(child)) || hasEvents(object)
-  // TODO: Optimize to not hit test on the whole scene
-  const objectsWithEvents = shallowRef((_scene.value?.children as TresInstance[]).filter(hasChildrenWithEvents) || [])
-
-  const eventHook = createEventHook<TresPointerEvent>()
-
-  /**
-   * propogateEvent
-   *
-   * Propogates an event to all intersected objects and their parents
-   * @param eventName - The name of the event to propogate
-   * @param event - The event object to propogate
-   */
-  function propogateEvent(eventName: string, event: TresEvent) {
-    // Array of objects we've already propogated to
-    const duplicates = []
-
-    // Flag that is set to true when the stopProgatingFn is called
-    const stopPropagatingFn = () => (event.stopPropagating = true)
-    event.stopPropagation = stopPropagatingFn
-
-    // Loop through all intersected objects and call their event handler
-    for (const intersection of event?.intersections) {
-      if (event.stopPropagating) { return }
-
-      // Add intersection data to event object
-      event = { ...event, ...intersection }
-
-      const { object } = intersection
-      event.eventObject = object as TresObject
-      executeEventListeners((object as Record<string, any>)[eventName], event)
-      duplicates.push(object)
-
-      // Propogate the event up the parent chain before moving on to the next intersected object
-      let parentObj = object.parent
-      while (parentObj !== null && !event.stopPropagating) {
-        // We've already been here, break the loop
-        if (duplicates.includes(parentObj)) {
-          break
-        }
-
-        // Sets eventObject to object that registered the event listener
-        event.eventObject = parentObj as TresObject
-        executeEventListeners((parentObj as Record<string, any>)[eventName], event)
-        duplicates.push(parentObj)
-        parentObj = parentObj.parent
-      }
-
-      // Convert eventName to kebab case and emit event from TresCanvas
-      const kebabEventName = hyphenate(eventName.slice(2)) as PointerEventType
-
-      eventHook.trigger({ type: kebabEventName, event, intersection })
-    }
-  }
-
-  const {
-    onClick,
-    onDblClick,
-    onContextMenu,
-    onPointerMove,
-    onPointerDown,
-    onPointerUp,
-    onPointerMissed,
-    onWheel,
-    forceUpdate,
-  } = useRaycaster(objectsWithEvents, context)
-
-  onPointerUp(event => propogateEvent('onPointerUp', event))
-  onPointerDown(event => propogateEvent('onPointerDown', event))
-  onClick(event => propogateEvent('onClick', event))
-  onDblClick(event => propogateEvent('onDoubleClick', event))
-  onContextMenu(event => propogateEvent('onContextMenu', event))
-  onWheel(event => propogateEvent('onWheel', event))
-
-  let prevIntersections: Intersection[] = []
-
-  onPointerMove((event) => {
-    // Current intersections mapped as meshes
-    const hits = event.intersections.map(({ object }) => object)
-
-    // Keep Backup of new intersections incase we overwrite due to a pointer out or leave event
-    const newIntersections = event.intersections as unknown as Intersection[]
-
-    // Previously intersected mesh is no longer intersected, fire onPointerLeave
-    prevIntersections.forEach(({ object: hit }) => {
-      if (
-        !hits.includes(hit as unknown as Object3D<Object3DEventMap>)
-      ) {
-        event.intersections = prevIntersections
-        propogateEvent('onPointerLeave', event)
-        propogateEvent('onPointerOut', event)
-      }
-    })
-
-    // Reset intersections to newIntersections
-    event.intersections = newIntersections
-
-    // Newly intersected mesh is not in the previous intersections, fire onPointerEnter
-    event.intersections.forEach(({ object: hit }) => {
-      if (!prevIntersections.includes(hit as unknown as Intersection)) {
-        propogateEvent('onPointerEnter', event)
-        propogateEvent('onPointerOver', event)
-      }
-    })
-
-    // Fire onPointerMove for all intersected objects
-    propogateEvent('onPointerMove', event)
-
-    // Update previous intersections
-    prevIntersections = event.intersections as unknown as Intersection[]
-  })
-
-  /**
-   * We need to track pointer missed objects separately
-   * since they will not be a part of the raycaster intersection
-   */
-  const pointerMissedObjects: TresObject[] = []
-  onPointerMissed((event: TresEvent) => {
-    // Flag that is set to true when the stopProgatingFn is called
-    const stopPropagatingFn = () => (event.stopPropagating = true)
-    event.stopPropagation = stopPropagatingFn
-
-    pointerMissedObjects.forEach((object: TresObject) => {
-      if (event.stopPropagating) { return }
-
-      // Set eventObject to object that registered the event
-      event.eventObject = object
-
-      executeEventListeners(object.onPointerMissed, event)
-    })
-
-    eventHook.trigger({ type: 'pointer-missed', event })
-  })
-
-  function registerObject(maybeTresObject: unknown) {
-    if (isTresObject(maybeTresObject) && isObject3D(maybeTresObject)) {
-      objectsWithEvents.value.push(maybeTresObject as TresInstance)
-    }
-  }
-
-  function deregisterObject(maybeTresObject: unknown) {
-    if (isTresObject(maybeTresObject) && isObject3D(maybeTresObject)) {
-      const index = objectsWithEvents.value.indexOf(maybeTresObject as TresInstance)
-      if (index > -1) {
-        objectsWithEvents.value.splice(index, 1)
-      }
-    }
-  }
-
-  function registerPointerMissedObject(maybeTresObject: unknown) {
-    if (isTresObject(maybeTresObject) && isObject3D(maybeTresObject) && maybeTresObject.onPointerMissed) {
-      pointerMissedObjects.push(maybeTresObject)
-    }
-  }
-
-  function deregisterPointerMissedObject(maybeTresObject: unknown) {
-    if (isTresObject(maybeTresObject) && isObject3D(maybeTresObject)) {
-      const index = pointerMissedObjects.indexOf(maybeTresObject)
-      if (index > -1) {
-        pointerMissedObjects.splice(index, 1)
-      }
-    }
-  }
-
-  // Attach methods to tres context
-  context.eventManager = {
-    onEvent: eventHook.on,
-    forceUpdate,
-    registerObject,
-    deregisterObject,
-    registerPointerMissedObject,
-    deregisterPointerMissedObject,
-  }
-
-  return {
-    onEvent: eventHook.on,
-    forceUpdate,
-    registerObject,
-    deregisterObject,
-    registerPointerMissedObject,
-    deregisterPointerMissedObject,
-  }
-}

+ 4 - 37
src/core/nodeOps.ts

@@ -7,23 +7,7 @@ import { logError } from '../utils/logger'
 import { isArray, isCamera, isClassInstance, isColor, isColorRepresentation, isCopyable, isFunction, isLayers, isObject, isObject3D, isScene, isTresInstance, isUndefined, isVectorLike } from '../utils/is'
 import { isArray, isCamera, isClassInstance, isColor, isColorRepresentation, isCopyable, isFunction, isLayers, isObject, isObject3D, isScene, isTresInstance, isUndefined, isVectorLike } from '../utils/is'
 import { createRetargetingProxy } from '../utils/primitive/createRetargetingProxy'
 import { createRetargetingProxy } from '../utils/primitive/createRetargetingProxy'
 import { catalogue } from './catalogue'
 import { catalogue } from './catalogue'
-
-const supportedPointerEvents = [
-  'onClick',
-  'onContextMenu',
-  'onPointerMove',
-  'onPointerEnter',
-  'onPointerLeave',
-  'onPointerOver',
-  'onPointerOut',
-  'onDoubleClick',
-  'onPointerDown',
-  'onPointerUp',
-  'onPointerCancel',
-  'onPointerMissed',
-  'onLostPointerCapture',
-  'onWheel',
-]
+import { isSupportedPointerEvent, pointerEventsMapVueToThree } from '../utils/pointerEvents'
 
 
 export const nodeOps: (context: TresContext) => RendererOptions<TresObject, TresObject | null> = (context) => {
 export const nodeOps: (context: TresContext) => RendererOptions<TresObject, TresObject | null> = (context) => {
   const scene = context.scene.value
   const scene = context.scene.value
@@ -90,7 +74,6 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
       ...(isTresInstance(obj) ? obj.__tres : {}),
       ...(isTresInstance(obj) ? obj.__tres : {}),
       type: name,
       type: name,
       memoizedProps: props,
       memoizedProps: props,
-      eventCount: 0,
       primitive: tag === 'primitive',
       primitive: tag === 'primitive',
       attach: props.attach,
       attach: props.attach,
     }, context)
     }, context)
@@ -111,15 +94,9 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     child = unboxTresPrimitive(childInstance)
     child = unboxTresPrimitive(childInstance)
     parent = unboxTresPrimitive(parentInstance)
     parent = unboxTresPrimitive(parentInstance)
 
 
-    if (child.__tres && child.__tres?.eventCount > 0) {
-      context.eventManager?.registerObject(child)
-    }
-
     if (isCamera(child)) {
     if (isCamera(child)) {
       context.camera?.registerCamera(child)
       context.camera?.registerCamera(child)
     }
     }
-    // NOTE: Track onPointerMissed objects separate from the scene
-    context.eventManager?.registerPointerMissedObject(child)
 
 
     if (childInstance.__tres.attach) {
     if (childInstance.__tres.attach) {
       attach(parentInstance, childInstance, childInstance.__tres.attach)
       attach(parentInstance, childInstance, childInstance.__tres.attach)
@@ -149,11 +126,6 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
 
 
     if (!node) { return }
     if (!node) { return }
 
 
-    // Remove from event manager if necessary
-    if (node?.__tres && node.__tres?.eventCount > 0) {
-      context.eventManager?.deregisterObject(node)
-    }
-
     // NOTE: Derive `dispose` value for this `remove` call and
     // NOTE: Derive `dispose` value for this `remove` call and
     // recursive remove calls.
     // recursive remove calls.
     dispose = isUndefined(dispose) ? 'default' : dispose
     dispose = isUndefined(dispose) ? 'default' : dispose
@@ -268,14 +240,9 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
       return
       return
     }
     }
 
 
-    if (isObject3D(node) && key === 'blocks-pointer-events') {
-      if (nextValue || nextValue === '') { node[key] = nextValue }
-      else { delete node[key] }
-      return
-    }
     // Has events
     // Has events
-    if (supportedPointerEvents.includes(prop) && node.__tres) {
-      node.__tres.eventCount += 1
+    if (isSupportedPointerEvent(prop) && isFunction(nextValue)) {
+      node.addEventListener(pointerEventsMapVueToThree[prop], nextValue)
     }
     }
     let finalKey = kebabToCamel(key)
     let finalKey = kebabToCamel(key)
     let target = root?.[finalKey] as Record<string, unknown>
     let target = root?.[finalKey] as Record<string, unknown>
@@ -347,7 +314,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     if (isFunction(target)) {
     if (isFunction(target)) {
       // don't call pointer event callback functions
       // don't call pointer event callback functions
 
 
-      if (!supportedPointerEvents.includes(prop)) {
+      if (!isSupportedPointerEvent(prop)) {
         if (isArray(value)) { node[finalKey](...value) }
         if (isArray(value)) { node[finalKey](...value) }
         else { node[finalKey](value) }
         else { node[finalKey](value) }
       }
       }

+ 3 - 78
src/types/index.ts

@@ -3,6 +3,7 @@ import type * as THREE from 'three'
 
 
 import type { DefineComponent, VNode, VNodeRef } from 'vue'
 import type { DefineComponent, VNode, VNodeRef } from 'vue'
 import type { TresContext } from '../composables/useTresContextProvider'
 import type { TresContext } from '../composables/useTresContextProvider'
+import type { PointerEventHandlers } from '../utils/pointerEvents'
 
 
 // Based on React Three Fiber types by Pmndrs
 // Based on React Three Fiber types by Pmndrs
 // https://github.com/pmndrs/react-three-fiber/blob/v9/packages/fiber/src/three-types.ts
 // https://github.com/pmndrs/react-three-fiber/blob/v9/packages/fiber/src/three-types.ts
@@ -23,10 +24,6 @@ export interface TresCatalogue {
   [name: string]: ConstructorRepresentation
   [name: string]: ConstructorRepresentation
 }
 }
 
 
-export const pointerEventTypes = ['click', 'double-click', 'context-menu', 'pointer-move', 'pointer-up', 'pointer-down', 'pointer-enter', 'pointer-leave', 'pointer-over', 'pointer-out', 'pointer-missed', 'wheel'] as const
-export type PointerEventType = typeof pointerEventTypes[number]
-export interface TresPointerEvent { type: PointerEventType, event: TresEvent, intersection?: Intersection }
-
 export type TresCamera = THREE.OrthographicCamera | THREE.PerspectiveCamera
 export type TresCamera = THREE.OrthographicCamera | THREE.PerspectiveCamera
 
 
 /**
 /**
@@ -51,9 +48,7 @@ interface TresBaseObject {
 
 
 export interface LocalState {
 export interface LocalState {
   type: string
   type: string
-  eventCount: number
   root: TresContext
   root: TresContext
-  handlers: Partial<EventHandlers>
   memoizedProps: { [key: string]: any }
   memoizedProps: { [key: string]: any }
   // NOTE:
   // NOTE:
   // LocalState holds information about the parent/child relationship
   // LocalState holds information about the parent/child relationship
@@ -93,77 +88,6 @@ export interface TresScene extends THREE.Scene {
   }
   }
 }
 }
 
 
-// Events
-
-export interface Intersection extends THREE.Intersection {
-  /** The event source (the object which registered the handler) */
-  eventObject: TresObject
-}
-
-export interface IntersectionEvent<TSourceEvent> extends Intersection {
-  /** The event source (the object which registered the handler) */
-  eventObject: TresObject
-  /** An array of intersections */
-  intersections: Intersection[]
-  /** vec3.set(pointer.x, pointer.y, 0).unproject(camera) */
-  unprojectedPoint: THREE.Vector3
-  /** Normalized event coordinates */
-  pointer: THREE.Vector2
-  /** Delta between first click and this event */
-  delta: number
-  /** The ray that pierced it */
-  ray: THREE.Ray
-  /** The camera that was used by the raycaster */
-  camera: THREE.Camera
-  /** stopPropagation will stop underlying handlers from firing */
-  stopPropagation: () => void
-  /** The original host event */
-  nativeEvent: TSourceEvent
-  /** If the event was stopped by calling stopPropagation */
-  stopped: boolean
-}
-
-export type ThreeEvent<TEvent> = IntersectionEvent<TEvent> & Properties<TEvent>
-export type DomEvent = PointerEvent | MouseEvent | WheelEvent
-
-export interface TresEvent {
-  eventObject: TresObject
-  event: DomEvent
-  stopPropagation: () => void
-  stopPropagating: boolean
-  intersections: Intersection[]
-  intersects: Intersection[]
-}
-
-export interface Events {
-  onClick: EventListener
-  onContextMenu: EventListener
-  onDoubleClick: EventListener
-  onWheel: EventListener
-  onPointerDown: EventListener
-  onPointerUp: EventListener
-  onPointerLeave: EventListener
-  onPointerMove: EventListener
-  onPointerCancel: EventListener
-  onLostPointerCapture: EventListener
-}
-
-export interface EventHandlers {
-  onClick?: (event: ThreeEvent<MouseEvent>) => void
-  onContextMenu?: (event: ThreeEvent<MouseEvent>) => void
-  onDoubleClick?: (event: ThreeEvent<MouseEvent>) => void
-  onPointerUp?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerDown?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerOver?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerOut?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerEnter?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerLeave?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerMove?: (event: ThreeEvent<PointerEvent>) => void
-  onPointerMissed?: (event: MouseEvent) => void
-  onPointerCancel?: (event: ThreeEvent<PointerEvent>) => void
-  onWheel?: (event: ThreeEvent<WheelEvent>) => void
-}
-
 interface MathRepresentation {
 interface MathRepresentation {
   set(...args: number[] | [THREE.ColorRepresentation]): any
   set(...args: number[] | [THREE.ColorRepresentation]): any
 }
 }
@@ -203,7 +127,8 @@ export type WithMathProps<P> = { [K in keyof P]: P[K] extends MathRepresentation
 interface RaycastableRepresentation {
 interface RaycastableRepresentation {
   raycast: (raycaster: THREE.Raycaster, intersects: THREE.Intersection[]) => void
   raycast: (raycaster: THREE.Raycaster, intersects: THREE.Intersection[]) => void
 }
 }
-type EventProps<P> = P extends RaycastableRepresentation ? Partial<EventHandlers> : unknown
+type EventProps<P> = P extends RaycastableRepresentation ? Partial<PointerEventHandlers> : unknown
+export type { TresPointerEvent } from '../utils/pointerEvents'
 
 
 export interface VueProps {
 export interface VueProps {
   children?: VNode[]
   children?: VNode[]

+ 0 - 6
src/utils/index.ts

@@ -414,9 +414,7 @@ export function prepareTresInstance<T extends TresObject>(obj: T, state: Partial
 
 
   instance.__tres = {
   instance.__tres = {
     type: 'unknown',
     type: 'unknown',
-    eventCount: 0,
     root: context,
     root: context,
-    handlers: {},
     memoizedProps: {},
     memoizedProps: {},
     objects: [],
     objects: [],
     parent: null,
     parent: null,
@@ -569,14 +567,10 @@ export function doRemoveDeregister(node: TresObject, context: TresContext) {
     if (isCamera(child)) {
     if (isCamera(child)) {
       context.camera.deregisterCamera(child)
       context.camera.deregisterCamera(child)
     }
     }
-    // deregisterAtPointerEventHandlerIfRequired?.(child as TresObject)
-    context.eventManager?.deregisterPointerMissedObject(child)
   })
   })
 
 
-  // NOTE: Deregister `node`
   if (isCamera(node)) {
   if (isCamera(node)) {
     context.camera.deregisterCamera(node)
     context.camera.deregisterCamera(node)
   }
   }
-  /*  deregisterAtPointerEventHandlerIfRequired?.(node as TresObject) */
   invalidateInstance(node as TresObject)
   invalidateInstance(node as TresObject)
 }
 }

+ 63 - 0
src/utils/pointerEvents.ts

@@ -0,0 +1,63 @@
+import type { Events } from 'vue'
+import type { PointerEvent } from '@pmndrs/pointer-events'
+
+// Custom Tres events that aren't part of Vue's Events type
+type CustomTresEvents = 'onLostpointercapture'
+
+// All supported pointer events (Vue Events + custom Tres events)
+type SupportedPointerEvents = Extract<keyof Events, 'onClick'
+  | 'onContextmenu'
+  | 'onPointermove'
+  | 'onPointerenter'
+  | 'onPointerleave'
+  | 'onPointerover'
+  | 'onPointerout'
+  | 'onDblclick'
+  | 'onPointerdown'
+  | 'onPointerup'
+  | 'onPointercancel'
+  | 'onWheel'> | CustomTresEvents
+
+export const supportedPointerEvents = [
+  'onClick',
+  'onContextmenu',
+  'onPointermove',
+  'onPointerenter',
+  'onPointerleave',
+  'onPointerover',
+  'onPointerout',
+  'onDblclick',
+  'onPointerdown',
+  'onPointerup',
+  'onPointercancel',
+  'onLostpointercapture',
+  'onWheel',
+] as const satisfies readonly SupportedPointerEvents[]
+
+export type SupportedVuePointerEvent = typeof supportedPointerEvents[number]
+
+export type TresPointerEventName = 'click' | 'contextmenu' | 'pointermove' | 'pointerenter' | 'pointerleave' | 'pointerover' | 'pointerout' | 'dblclick' | 'pointerdown' | 'pointerup' | 'pointercancel' | 'lostpointercapture' | 'wheel'
+
+export const pointerEventsMapVueToThree: Record<SupportedVuePointerEvent, TresPointerEventName> = {
+  onClick: 'click',
+  onContextmenu: 'contextmenu',
+  onPointermove: 'pointermove',
+  onPointerenter: 'pointerenter',
+  onPointerleave: 'pointerleave',
+  onPointerover: 'pointerover',
+  onPointerout: 'pointerout',
+  onDblclick: 'dblclick',
+  onPointerdown: 'pointerdown',
+  onPointerup: 'pointerup',
+  onPointercancel: 'pointercancel',
+  onLostpointercapture: 'lostpointercapture',
+  onWheel: 'wheel',
+}
+
+export const isSupportedPointerEvent = (event: string): event is SupportedVuePointerEvent =>
+  supportedPointerEvents.includes(event as SupportedVuePointerEvent)
+
+export type TresPointerEvent = PointerEvent<MouseEvent>
+export type PointerEventHandlers = {
+  [key in SupportedVuePointerEvent]: (event: TresPointerEvent) => void
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov