Explorar o código

Went a different direction with this that I believe is simpler to reason about and aligns more closely to how a user would expect this to work

Garrett Walker hai 1 ano
pai
achega
408ce647a8

+ 54 - 16
playground/src/components/TheExperience.vue

@@ -1,36 +1,39 @@
 <script setup lang="ts">
-import { extend } from '@tresjs/core';
+import { extend } from "@tresjs/core";
 
-import { reactive } from 'vue';
-import { BasicShadowMap, SRGBColorSpace, NoToneMapping } from 'three';
-import { TresCanvas } from '@tresjs/core';
-import { OrbitControls } from '@tresjs/cientos';
+import { reactive } from "vue";
+import { BasicShadowMap, SRGBColorSpace, NoToneMapping } from "three";
+import { TresCanvas } from "@tresjs/core";
+import { OrbitControls } from "@tresjs/cientos";
 
 const gl = {
-  clearColor: '#82DBC5',
+  clearColor: "#82DBC5",
   shadows: true,
   alpha: false,
   shadowMapType: BasicShadowMap,
   outputColorSpace: SRGBColorSpace,
   toneMapping: NoToneMapping,
 };
+
+// const noop = () => {}
+const noop = console.log;
 </script>
 
 <template>
   <TresCanvas v-bind="gl">
     <TresPerspectiveCamera :position="[9, 9, 9]" />
     <OrbitControls />
-    <TresGroup 
-      @pointer-move="console.log('Group pointer-move')"
-      @pointer-enter="console.log('Group pointer-enter')"
-      @pointer-leave="console.log('Group pointer-leave')"
-      @click="console.log('Group click')"
+    <TresGroup
+      @pointer-move="noop('Group pointer-move')"
+      @pointer-enter="noop('Group pointer-enter')"
+      @pointer-leave="noop('Group pointer-leave')"
+      @click="noop('Group click')"
     >
-      <TresMesh 
-        @pointer-move="console.log('child pointer-move')"
-        @pointer-enter="console.log('child pointer-enter')"
-        @pointer-leave="console.log('child pointer-leave')"
-        @click="console.log('child click')"
+      <TresMesh
+        @pointer-move="noop('child pointer-move')"
+        @pointer-enter="noop('child pointer-enter')"
+        @pointer-leave="noop('child pointer-leave')"
+        @click="noop('child click')"
         :position="[-2, 2, 0]"
         :rotation="[0, Math.PI, 0]"
       >
@@ -40,10 +43,45 @@ const gl = {
       <TresMesh :position="[0, 0, 0]" cast-shadow>
         <TresBoxGeometry :args="[1.5, 1.5, 1.5]" />
         <TresMeshToonMaterial color="#4F4F4F" />
+        <TresMesh :position="[5, 2, -5]" cast-shadow>
+          <TresBoxGeometry :args="[1.5, 1.5, 1.5]" />
+          <TresMeshToonMaterial color="#d3d3d3" />
+        </TresMesh>
       </TresMesh>
       <TresMesh :position="[2, -2, 0]">
         <TresSphereGeometry />
         <TresMeshToonMaterial color="#FBB03B" />
+        <TresGroup
+          :position="[4, -2, -10]"
+          @pointer-move="noop('2nd Group pointer-move')"
+          @pointer-enter="noop('2nd Group pointer-enter')"
+          @pointer-leave="noop('2nd Group pointer-leave')"
+          @click="noop('2nd Group click')"
+        >
+          <TresMesh
+            @pointer-move="noop('2nd group child pointer-move')"
+            @pointer-enter="noop('2nd group child pointer-enter')"
+            @pointer-leave="noop('2nd group child pointer-leave')"
+            @click="noop('2nd group child click')"
+            :position="[-2, 2, 0]"
+            :rotation="[0, Math.PI, 0]"
+          >
+            <TresConeGeometry :args="[1, 1.5, 3]" />
+            <TresMeshToonMaterial color="#82DBC5" />
+          </TresMesh>
+          <TresMesh :position="[0, 0, 0]" cast-shadow>
+            <TresBoxGeometry :args="[1.5, 1.5, 1.5]" />
+            <TresMeshToonMaterial color="#4F4F4F" />
+            <TresMesh :position="[5, 2, -5]" cast-shadow>
+              <TresBoxGeometry :args="[1.5, 1.5, 1.5]" />
+              <TresMeshToonMaterial color="#d3d3d3" />
+            </TresMesh>
+          </TresMesh>
+          <TresMesh :position="[2, -2, 0]">
+            <TresSphereGeometry />
+            <TresMeshToonMaterial color="#FBB03B" />
+          </TresMesh>
+        </TresGroup>
       </TresMesh>
     </TresGroup>
     <TresDirectionalLight :position="[0, 2, 4]" :intensity="1.2" cast-shadow />

+ 34 - 23
src/composables/usePointerEventHandler/index.ts

@@ -45,12 +45,29 @@ export const usePointerEventHandler = (
     deregisterBlockingObject(object)
   }
 
-  const combineFunctions = (groupFn: CallbackFn, childFn: CallbackFn): CallbackFn | CallbackFnPointerLeave => {
-    return (intersection: Intersection<Object3D<Event>>, event: PointerEvent) => {
-      // Emit child events first
-      childFn?.(intersection, event);
-      groupFn?.(intersection, event);
-    }
+  /**
+   * Recursive functions that bubbles events up the scene graph
+   * 
+   * @param ancestor - Ancestor to bubble event to
+   * @param eventArgs
+   * @param eventMap Any of the {click|move|enter|leave} object to callbackFn mappers
+   * @returns void
+   */
+  const bubbleEvent = (
+    ancestor: Object3D | null, 
+    eventArgs: [Intersection<Object3D<Event>>[] | Object3D<Event>, PointerEvent], 
+    eventMap: Map<Object3D, CallbackFn> | Map<Object3D, CallbackFnPointerLeave>,
+  ) => {
+    // Early return if ancestor is undefined
+    if (!ancestor)
+      return
+
+    // If this ancestor has an event listener wired up, emit this event
+    eventMap?.get(ancestor)?.(...eventArgs)
+
+    // Recursively bubble the event up to our ancestors
+    if (ancestor?.parent)
+      bubbleEvent(ancestor.parent, eventArgs, eventMap)
   }
 
   const registerObject = (object: Object3D & EventProps) => {
@@ -60,19 +77,6 @@ export const usePointerEventHandler = (
     if (onPointerMove) objectsWithEventListeners.pointerMove.set(object, onPointerMove)
     if (onPointerEnter) objectsWithEventListeners.pointerEnter.set(object, onPointerEnter)
     if (onPointerLeave) objectsWithEventListeners.pointerLeave.set(object, onPointerLeave)
-
-    // If object is a TresGroup, attach it's event handlers to all children.
-    // If child already has an event attached, join the 2 functions via a callback
-    if(object?.isGroup){
-      for(const child of object?.children) {
-        const { onClick: onChildClick, onPointerMove: onChildPointerMove, onPointerEnter: onChildPointerEnter, onPointerLeave: onChildPointerLeave } = child
-
-        if (onClick) objectsWithEventListeners.click.set(child, combineFunctions(onClick, onChildClick))
-        if (onPointerMove) objectsWithEventListeners.pointerMove.set(child, combineFunctions(onPointerMove, onChildPointerMove))
-        if (onPointerEnter) objectsWithEventListeners.pointerEnter.set(child, combineFunctions(onPointerEnter, onChildPointerEnter))
-        if (onPointerLeave) objectsWithEventListeners.pointerLeave.set(child, combineFunctions(onPointerLeave, onChildPointerLeave))
-      }
-    }
   }
 
   // to make the registerObject available in the custom renderer (nodeOps), it is attached to the scene
@@ -97,7 +101,10 @@ export const usePointerEventHandler = (
   const { onClick, onPointerMove } = useRaycaster(objectsToWatch, contextParts)
 
   onClick(({ intersects, event }) => {
-    if (intersects.length) objectsWithEventListeners.click.get(intersects[0].object)?.(intersects[0], event)
+    if (intersects.length) {      
+      objectsWithEventListeners.click.get(intersects[0].object)?.(intersects[0], event)
+      bubbleEvent(intersects[0].object.parent, [intersects[0], event], objectsWithEventListeners.click)
+    }
   })
 
   let previouslyIntersectedObject: Object3D<Event> | null
@@ -107,15 +114,19 @@ export const usePointerEventHandler = (
 
     const { pointerLeave, pointerEnter, pointerMove } = objectsWithEventListeners
 
-    if (previouslyIntersectedObject && previouslyIntersectedObject !== firstObject)
+    if (previouslyIntersectedObject && previouslyIntersectedObject !== firstObject) {
       pointerLeave.get(previouslyIntersectedObject)?.(previouslyIntersectedObject, event)
-    
+      bubbleEvent(previouslyIntersectedObject.parent, [previouslyIntersectedObject, event], pointerLeave)
+    }
 
     if (firstObject) {
-      if (previouslyIntersectedObject !== firstObject) 
+      if (previouslyIntersectedObject !== firstObject) {
         pointerEnter.get(firstObject)?.(intersects[0], event)
+        bubbleEvent(firstObject.parent, [intersects[0], event], pointerEnter)
+      }
 
       pointerMove.get(firstObject)?.(intersects[0], event)
+      bubbleEvent(firstObject.parent, [intersects[0], event], pointerMove)
     }
 
     previouslyIntersectedObject = firstObject || null