Răsfoiți Sursa

Merge pull request #89 from Tresjs/feature/6-events

Feature/6 events
Alvaro Saburido 2 ani în urmă
părinte
comite
8b4bcbea5f

+ 4 - 0
docs/.vitepress/config.ts

@@ -32,6 +32,10 @@ export default defineConfig({
             text: 'Composables',
             link: '/api/composables',
           },
+          {
+            text: 'Events',
+            link: '/api/events',
+          },
         ],
       },
       {

+ 27 - 0
docs/api/events.md

@@ -0,0 +1,27 @@
+# Events <Badge type="warning" text="^1.6.0" />
+
+**TresJS** Mesh objects emit pointer events when they are interacted with using `raycaster` and `pointer` objects under the hood.
+
+```html
+<TresMesh
+  @click="(ev) => console.log('click', ev)"
+  @pointer-move="(ev) => console.log('click', ev)"
+  @pointer-enter="(ev) => console.log('click', ev)"
+  @pointer-leave="(ev) => console.log('click', ev)"
+/>
+```
+
+## Event Data
+
+The event data is a `TresEvent` object that contains the following properties:
+
+```ts
+;({
+  object: Object3D, // The mesh object that emitted the event
+  distance: number, // The distance between the camera and the mesh
+  point: Vector3, // The intersection point between the ray and the mesh
+  uv: Vector2, // The uv coordinates of the intersection point
+  face: Face3, // The face of the mesh that was intersected
+  faceIndex: number, // The index of the face that was intersected
+})
+```

+ 1 - 1
docs/index.md

@@ -6,7 +6,7 @@ titleTemplate: The solution for 3D on VueJS
 
 hero:
   name: TresJS
-  text: Bring ThreeJS to VueJS ecosystem
+  text: Bring Three to the Vue ecosystem
   tagline: Create awesome 3D experiences with the framework you love.
   image:
     src: /hero.png

+ 2 - 2
packages/tres/src/App.vue

@@ -1,12 +1,12 @@
 <script setup lang="ts">
 import { useTweakPane } from '@tresjs/cientos'
-import AnimatedModel from '/@/components/AnimatedModel.vue'
+import TheEvents from '/@/components/TheEvents.vue'
 
 useTweakPane()
 </script>
 
 <template>
   <Suspense>
-    <AnimatedModel />
+    <TheEvents />
   </Suspense>
 </template>

+ 59 - 0
packages/tres/src/components/TheEvents.vue

@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { BasicShadowMap, NoToneMapping, sRGBEncoding } from 'three'
+import { reactive } from 'vue'
+import { OrbitControls } from '@tresjs/cientos'
+
+const state = reactive({
+  clearColor: '#201919',
+  shadows: true,
+  alpha: false,
+  shadowMapType: BasicShadowMap,
+  outputEncoding: sRGBEncoding,
+  toneMapping: NoToneMapping,
+})
+
+function onClick(ev) {
+  if (ev) {
+    ev.object.material.color.set('#00ffff')
+  }
+}
+
+function onPointerEnter(ev) {
+  console.log(ev)
+  if (ev) {
+    ev.object.material.color.set('#ff0000')
+  }
+}
+
+function onPointerLeave(ev) {
+  if (ev) {
+    /*  ev.object.material.color.set('#efefef') */
+  }
+}
+</script>
+
+<template>
+  <TresCanvas v-bind="state">
+    <TresPerspectiveCamera :position="[11, 11, 11]" :fov="45" :near="0.1" :far="1000" :look-at="[-8, 3, -3]" />
+    <OrbitControls />
+    <TresScene>
+      <TresDirectionalLight :position="[0, 8, 4]" :intensity="0.2" cast-shadow />
+      <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]"
+            :key="[x, y, z]"
+            :position="[x, y, z]"
+            @click="onClick"
+            @pointer-enter="onPointerEnter"
+            @pointer-leave="onPointerLeave"
+          >
+            <TresBoxGeometry :args="[1, 1, 1]" />
+            <TresMeshToonMaterial color="#efefef" />
+          </TresMesh>
+        </template>
+      </template>
+      <TresAmbientLight :intensity="0.5" />
+    </TresScene>
+  </TresCanvas>
+</template>

+ 24 - 10
packages/tres/src/components/TheExperience.vue

@@ -91,6 +91,21 @@ pane
     console.log(ev.value)
     state.toneMapping = ev.value
   })
+
+function onPointerEnter(ev) {
+  console.log('onPointerEnter', ev)
+}
+
+function onPointerMove(ev) {
+  console.log('onPointerMove', ev)
+}
+
+function onPointerLeave(ev) {
+  console.log('onPointerLeave', ev)
+}
+function onClick(ev) {
+  console.log('click', ev)
+}
 </script>
 <template>
   <TresCanvas v-bind="state">
@@ -98,18 +113,17 @@ pane
     <OrbitControls make-default />
     <TresScene>
       <TresAmbientLight :intensity="0.5" />
-      <!--  <TresMesh :position="[-2, 6, 0]" :rotation="[0, Math.PI, 0]" cast-shadow>
-        <TresConeGeometry :args="[1, 1.5, 3]" />
-        <TresMeshToonMaterial color="#82DBC5" />
-      </TresMesh> -->
-      <!-- <TransformControls :object="boxRef" mode="rotate" />
-      <TresMesh ref="boxRef" :position="[0, 4, 0]" cast-shadow>
-        <TresBoxGeometry :args="[1.5, 1.5, 1.5]" />
-        <TresMeshToonMaterial color="#4F4F4F" />
-      </TresMesh> -->
       <TransformControls mode="scale" :object="sphereRef" />
 
-      <TresMesh ref="sphereRef" :position="[0, 4, 0]" cast-shadow>
+      <TresMesh
+        ref="sphereRef"
+        :position="[0, 4, 0]"
+        cast-shadow
+        @pointer-enter="onPointerEnter"
+        @pointer-move="onPointerMove"
+        @pointer-leave="onPointerLeave"
+        @click="onClick"
+      >
         <TresSphereGeometry />
         <TresMeshToonMaterial color="#FBB03B" />
       </TresMesh>

+ 1 - 2
packages/tres/src/core/useCatalogue/index.ts

@@ -10,7 +10,7 @@ delete catalogue.value.Scene
 
 let localApp: App
 export function useCatalogue(app?: App, prefix = 'Tres') {
-  const { logMessage, logError } = useLogger()
+  const { logError } = useLogger()
   if (!localApp && app) {
     localApp = app
   }
@@ -23,7 +23,6 @@ export function useCatalogue(app?: App, prefix = 'Tres') {
     }
     catalogue.value = Object.assign(catalogue.value, objects)
     const components = createComponentInstances(ref(objects))
-    logMessage('Adding objects to catalogue', { objects, catalogue: catalogue.value.uuid })
 
     if (localApp) {
       components.forEach(([key, cmp]) => {

+ 48 - 28
packages/tres/src/core/useInstanceCreator/index.ts

@@ -1,17 +1,19 @@
 /* eslint-disable new-cap */
 /* eslint-disable @typescript-eslint/no-empty-function */
-import { BufferAttribute, FogBase, OrthographicCamera, PerspectiveCamera, Scene } from 'three'
-import { defineComponent, inject, Ref } from 'vue'
+import { BufferAttribute, FogBase, OrthographicCamera, PerspectiveCamera, Raycaster, Scene } from 'three'
+import { defineComponent, inject, onUnmounted, Ref } from 'vue'
+import { useEventListener } from '@vueuse/core'
+
 import { isArray, isDefined, isFunction } from '@alvarosabu/utils'
 import { normalizeVectorFlexibleParam } from '/@/utils/normalize'
-import { useCamera, useCatalogue, useScene } from '/@/core/'
+import { useCamera, useCatalogue, useRenderLoop, useScene } from '/@/core/'
 import { useLogger } from '/@/composables'
-import { TresAttributes, TresCatalogue, TresInstance, TresVNode, TresVNodeType } from '/@/types'
+import { TresAttributes, TresCatalogue, TresInstance, TresVNode, TresVNodeType, TresEvent } from '/@/types'
 
 const VECTOR3_PROPS = ['rotation', 'scale', 'position']
 
 export function useInstanceCreator(prefix: string) {
-  const { logMessage, logError } = useLogger()
+  const { /* logMessage, */ logError } = useLogger()
 
   function processSetAttributes(props: Record<string, any>, instance: TresInstance) {
     if (!isDefined(props)) return
@@ -114,7 +116,6 @@ export function useInstanceCreator(prefix: string) {
           processProps(vnode.props, internalInstance)
         }
       }
-      logMessage(`Created ${vNodeType} instance`, internalInstance)
       return internalInstance
     }
   }
@@ -157,10 +158,13 @@ export function useInstanceCreator(prefix: string) {
           const name = `${prefix}${key}`
           const cmp = defineComponent({
             name,
-            setup(props, { slots, attrs, ...ctx }) {
+            setup(_props, { slots, attrs, ...ctx }) {
               const { scene: fallback } = useScene()
+              const { onLoop } = useRenderLoop()
               const scene = inject<Ref<Scene>>('local-scene') || fallback
-              const catalogue = inject<Ref<TresCatalogue>>('catalogue')
+              /* const { raycaster } = useRaycaster() */
+              const raycaster = inject<Ref<Raycaster>>('raycaster') /* 
+              const currentInstance = inject<Ref>('currentInstance') */
               const { pushCamera } = useCamera()
 
               let instance = createInstance(threeObj, attrs, slots)
@@ -175,6 +179,41 @@ export function useInstanceCreator(prefix: string) {
                 scene?.value.add(instance)
               }
 
+              let prevInstance: TresEvent | null = null
+              let currentInstance: TresEvent | null = null
+              if (instance.isMesh) {
+                onLoop(() => {
+                  if (instance && raycaster?.value) {
+                    const intersects = raycaster?.value.intersectObjects(scene.value.children)
+
+                    if (intersects.length > 0) {
+                      currentInstance = intersects[0]
+
+                      if (prevInstance === null || prevInstance.object.uuid !== currentInstance?.object.uuid) {
+                        ctx.emit('pointer-enter', currentInstance)
+                      }
+
+                      ctx.emit('pointer-move', currentInstance)
+                    } else {
+                      currentInstance = null
+                      if (prevInstance !== null) {
+                        ctx.emit('pointer-leave', prevInstance)
+                      }
+                    }
+
+                    prevInstance = currentInstance
+                  }
+                })
+
+                const clickEventListener = useEventListener(window, 'click', () => {
+                  ctx.emit('click', prevInstance)
+                })
+
+                onUnmounted(() => {
+                  clickEventListener()
+                })
+              }
+
               if (scene?.value && instance.isFog) {
                 scene.value.fog = instance as unknown as FogBase
               }
@@ -191,30 +230,11 @@ export function useInstanceCreator(prefix: string) {
                   if (instance.isObject3D) {
                     scene?.value.add(instance)
                   }
-
-                  logMessage(name, {
-                    instance,
-                    sceneuuid: scene?.value.uuid,
-                    catalogue: catalogue?.value.uuid,
-                    props,
-                    slots: slots.default ? slots.default() : undefined,
-                    attrs,
-                    ctx,
-                    scene,
-                  })
                 })
               }
 
               ctx.expose(instance)
-              logMessage(name, {
-                sceneuuid: scene?.value.uuid,
-                catalogue: catalogue?.value.uuid,
-                props,
-                slots: slots.default ? slots.default() : undefined,
-                attrs,
-                ctx,
-                scene,
-              })
+
               return () => {}
             },
           })

+ 27 - 0
packages/tres/src/core/useRaycaster/index.ts

@@ -0,0 +1,27 @@
+import { Raycaster, Vector2 } from 'three'
+import { onUnmounted, provide, ref, shallowRef } from 'vue'
+
+const raycaster = shallowRef(new Raycaster())
+const pointer = ref(new Vector2())
+const currentInstance = ref(null)
+
+export function useRaycaster() {
+  provide('raycaster', raycaster)
+  provide('pointer', pointer)
+  provide('currentInstance', currentInstance)
+
+  function onPointerMove(event: MouseEvent) {
+    pointer.value.x = (event.clientX / window.innerWidth) * 2 - 1
+    pointer.value.y = -(event.clientY / window.innerHeight) * 2 + 1
+  }
+
+  window.addEventListener('pointermove', onPointerMove)
+
+  onUnmounted(() => {
+    window.removeEventListener('pointermove', onPointerMove)
+  })
+  return {
+    raycaster,
+    pointer,
+  }
+}

+ 5 - 1
packages/tres/src/core/useScene/component.ts

@@ -3,6 +3,7 @@ import type { Renderer } from 'three'
 import { defineComponent, inject, provide, Ref } from 'vue'
 import { useRenderLoop } from '../useRenderLoop'
 import { useScene } from './'
+import { useRaycaster } from '../useRaycaster'
 
 /**
  * Vue component for rendering a Tres component.
@@ -13,11 +14,14 @@ export const Scene = defineComponent({
     const { scene } = useScene()
     const renderer = inject<Ref<Renderer>>('renderer')
     const { activeCamera } = useCamera()
+    const { raycaster, pointer } = useRaycaster()
     const { onLoop } = useRenderLoop()
 
     provide('local-scene', scene)
 
-    onLoop(({ clock }) => {
+    onLoop(() => {
+      raycaster.value.setFromCamera(pointer.value, activeCamera.value)
+
       if (renderer?.value && activeCamera?.value && scene?.value) {
         renderer.value.render(scene?.value, activeCamera.value)
       }

+ 2 - 2
packages/tres/src/core/useTres/index.ts

@@ -1,5 +1,5 @@
 import { WebGLRenderer } from 'three'
-import { reactive, toRefs } from 'vue'
+import { shallowReactive, toRefs } from 'vue'
 import { Camera } from '/@/core'
 
 export interface TresState {
@@ -8,7 +8,7 @@ export interface TresState {
   [key: string]: any
 }
 
-const state: TresState = reactive({})
+const state: TresState = shallowReactive({})
 
 export function useTres() {
   function getState(key: string) {

+ 2 - 1
packages/tres/src/index.ts

@@ -1,7 +1,7 @@
 import { App, Component } from 'vue'
 import { TresCanvas } from '/@/core/useRenderer/component'
 import { Scene } from '/@/core/useScene/component'
-import { useCatalogue, useInstanceCreator } from '/@/core'
+import { useCatalogue, useInstanceCreator, useTres } from '/@/core'
 export * from '/@/core'
 export * from './keys'
 import { version } from '../package.json'
@@ -22,6 +22,7 @@ const plugin: TresPlugin = {
     const { catalogue, extend } = useCatalogue(app, prefix)
     app.provide('catalogue', catalogue)
     app.provide('extend', extend)
+    app.provide('useTres', useTres())
     const { createComponentInstances } = useInstanceCreator(prefix)
     const components = createComponentInstances(catalogue)
     /*  const components = createComponentInstances(

+ 10 - 0
packages/tres/src/types/index.d.ts

@@ -13,6 +13,16 @@ export type TresVNode = VNode & { children?: Array<VNode | { default: any }>; ty
 export type TresAttributes = Record<string, any> & { args?: number[] }
 
 export type TresColor = string | number | Color | number[]
+
+export interface TresEvent extends Intersection<Object3D<Event>> {
+  object: Object3D
+  distance: number
+  face?: Face3
+  faceIndex?: number | undefined
+  point: Vector3
+  uv?: Vector2
+}
+
 declare global {
   // Define the window interface, with type annotations for the properties and methods of the window object
   interface Window {