Bladeren bron

Merge pull request #187 from Tresjs/bugfix/raycaster-is-broken-provider-context

fix: move raycaster logic from nodeOps to TresCanvas
Alvaro Saburido 2 jaren geleden
bovenliggende
commit
cce37bb87a

+ 3 - 3
docs/guide/index.md

@@ -15,15 +15,15 @@ TresJS v2 is still in alpha stage, so expect breaking changes until the first st
 ::: code-group
 
 ```bash [npm]
-npm install three @tresjs/core@alpha
+npm install three @tresjs/core@beta
 ```
 
 ```bash [yarn]
-yarn add three @tresjs/core@alpha
+yarn add three @tresjs/core@beta
 ```
 
 ```bash [pnpm]
-pnpm add three @tresjs/core@alpha
+pnpm add three @tresjs/core@beta
 ```
 
 :::

+ 11 - 6
playground/src/components/TheBasic.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import { sRGBEncoding, BasicShadowMap, NoToneMapping  } from 'three'
+import { sRGBEncoding, BasicShadowMap, NoToneMapping } from 'three'
 import { reactive, ref } from 'vue'
 import { TresCanvas } from '/@/components/TresCanvas'
-import { OrbitControls, TransformControls  } from '@tresjs/cientos'
+import { OrbitControls, TransformControls } from '@tresjs/cientos'
 import { useRenderLoop } from '/@/'
 
 const state = reactive({
@@ -23,16 +23,21 @@ onLoop(({ elapsed }) => {
   if (!sphereRef.value) return
   sphereRef.value.position.y += Math.sin(elapsed) * 0.01
 })
+
+function onPointerEnter(ev) {
+  if (ev) {
+    ev.object.material.color.set('#DFFF45')
+  }
+}
 </script>
 <template>
   <TresCanvas v-bind="state">
-    <TresPerspectiveCamera :position="[5, 5, 5]" :fov="45" :near="0.1" :far="1000"
-    :look-at="[0,0,0]" />
+    <TresPerspectiveCamera :position="[5, 5, 5]" :fov="45" :near="0.1" :far="1000" :look-at="[0, 0, 0]" />
     <OrbitControls />
     <TresAmbientLight :intensity="0.5" />
 
-    <TresMesh ref="sphereRef" :position="[0, 4, 0]" cast-shadow>
-      <TresSphereGeometry :args="[2,32,32]"/>
+    <TresMesh ref="sphereRef" :position="[0, 4, 0]" cast-shadow @pointer-enter="onPointerEnter">
+      <TresSphereGeometry :args="[2, 32, 32]" />
       <TresMeshToonMaterial color="cyan" />
     </TresMesh>
 

+ 1 - 1
playground/src/components/TheEvents.vue

@@ -37,7 +37,6 @@ function onPointerLeave(ev) {
     <TresPerspectiveCamera :position="[11, 11, 11]" :fov="45" :near="0.1" :far="1000" :look-at="[-8, 3, -3]" />
 
     <TresDirectionalLight :position="[0, 8, 4]" :intensity="0.2" cast-shadow />
-    <OrbitControls />
     <template v-for="x in [-2.5, 0, 2.5]">
       <template v-for="y in [-2.5, 0, 2.5]">
         <TresMesh
@@ -53,6 +52,7 @@ function onPointerLeave(ev) {
         </TresMesh>
       </template>
     </template>
+    <OrbitControls />
     <TresAmbientLight :intensity="0.5" />
   </TresCanvas>
 </template>

+ 1 - 1
playground/src/pages/index.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts"></script>
 <template>
   <Suspense>
-    <MultipleCanvas />
+    <TheEvents />
   </Suspense>
 </template>

+ 28 - 1
src/components/TresScene.ts

@@ -6,7 +6,8 @@ import { useLogger } from '/@/composables'
 import { useCamera, useRenderer, useRenderLoop, useRaycaster, useTres } from '/@/composables'
 import { extend } from '/@/core/catalogue'
 import { RendererPresetsType } from '/@/composables/useRenderer/const'
-import { TresObject } from '../types'
+import { TresEvent, TresObject } from '../types'
+import { useEventListener } from '@vueuse/core'
 
 export interface TresSceneProps {
   shadows?: boolean
@@ -80,12 +81,38 @@ export const TresScene = defineComponent<TresSceneProps>({
 
       const { raycaster, pointer } = useRaycaster()
 
+      let prevInstance: TresEvent | null = null
+      let currentInstance: TresEvent | null = null
+
       watchEffect(() => {
         if (activeCamera.value) raycaster.value.setFromCamera(pointer.value, activeCamera.value)
       })
 
       onLoop(() => {
         if (activeCamera.value) 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)
+            }
+          } 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)
       })
     }
 

+ 10 - 10
src/composables/useRaycaster/index.ts

@@ -1,10 +1,6 @@
 import { useTres } from '/@/composables'
 import { Raycaster, Vector2 } from 'three'
-import { Ref, ref, ShallowRef, shallowRef } from 'vue'
-
-const raycaster = shallowRef(new Raycaster())
-const pointer = ref(new Vector2())
-const currentInstance = ref(null)
+import { onUnmounted, Ref, ref, ShallowRef, shallowRef } from 'vue'
 
 /**
  * Raycaster composable return type
@@ -37,7 +33,11 @@ export interface UseRaycasterReturn {
  * @return {*} {UseRaycasterReturn}
  */
 export function useRaycaster(): UseRaycasterReturn {
-  const { setState } = useTres()
+  const raycaster = shallowRef(new Raycaster())
+  const pointer = ref(new Vector2())
+  const currentInstance = ref(null)
+  const { setState, state } = useTres()
+
   setState('raycaster', raycaster.value)
   setState('pointer', pointer)
   setState('currentInstance', currentInstance)
@@ -47,11 +47,11 @@ export function useRaycaster(): UseRaycasterReturn {
     pointer.value.y = -(event.clientY / window.innerHeight) * 2 + 1
   }
 
-  window.addEventListener('pointermove', onPointerMove) //TODO listener should be on canvas
+  state?.renderer?.domElement.addEventListener('pointermove', onPointerMove)
 
-  /*  onUnmounted(() => { TODO
-    window.removeEventListener('pointermove', onPointerMove)
-  }) */
+  onUnmounted(() => {
+    state?.renderer?.domElement.removeEventListener('pointermove', onPointerMove)
+  })
   return {
     raycaster,
     pointer,

+ 11 - 49
src/core/nodeOps.ts

@@ -1,21 +1,15 @@
-import { BufferAttribute, Mesh } from 'three'
-import { useCamera, useRaycaster, useRenderLoop, useLogger } from '/@/composables'
+import { BufferAttribute } from 'three'
+import { useCamera, useLogger } from '/@/composables'
 import { RendererOptions } from 'vue'
 import { catalogue } from './catalogue'
-import { isFunction, useEventListener } from '@vueuse/core'
-import { TresEvent, TresObject } from '../types'
+import { isFunction } from '@vueuse/core'
+import { TresObject } from '../types'
 import { isHTMLTag, kebabToCamel } from '../utils'
 
 const { logWarning } = useLogger()
 
-function hasEvents(obj: any) {
-  for (const prop in obj) {
-    if (prop.indexOf('on') === 0) {
-      return true
-    }
-  }
-  return false
-}
+const onRE = /^on[^a-z]/
+export const isOn = (key: string) => onRE.test(key)
 
 function noop(fn: string): any {
   fn
@@ -54,6 +48,8 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
       else if (instance.isBufferGeometry) instance.attach = 'geometry'
     }
 
+    instance.events = {}
+
     return instance
   },
   insert(child, parent, anchor) {
@@ -72,43 +68,6 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
         parent[child.attach] = child
       }
     }
-
-    const { onLoop } = useRenderLoop()
-
-    // RayCasting
-    let prevInstance: TresEvent | null = null
-    let currentInstance: TresEvent | null = null
-
-    if (child && child instanceof Mesh && hasEvents(child)) {
-      const { raycaster } = useRaycaster()
-      onLoop(() => {
-        if (parent?.children && child && raycaster) {
-          const intersects = raycaster.value.intersectObjects(parent.children)
-
-          if (intersects.length > 0 && intersects[0].object.uuid === child.uuid) {
-            currentInstance = intersects[0]
-
-            if (prevInstance === null || prevInstance.object.uuid !== currentInstance?.object.uuid) {
-              child.onPointerEnter?.(currentInstance)
-            }
-
-            child.onPointerMove?.(currentInstance)
-          } else {
-            currentInstance = null
-            if (prevInstance !== null) {
-              child.onPointerLeave?.(prevInstance)
-            }
-          }
-
-          prevInstance = currentInstance
-        }
-      })
-
-      useEventListener(window, 'click', () => {
-        if (currentInstance === null) return
-        child.onClick?.(currentInstance)
-      })
-    }
   },
   remove(node) {
     if (!node) return
@@ -144,6 +103,9 @@ export const nodeOps: RendererOptions<TresObject, TresObject> = {
 
         if (!target?.set) root = chain.reduce((acc, key) => acc[kebabToCamel(key)], root)
       }
+      if (isOn(key)) {
+        node.events[key] = nextValue
+      }
       let value = nextValue
       if (value === '') value = true
       // Set prop, prefer atomic methods if applicable