浏览代码

feat: editable scenes from devtools

alvarosabu 1 年之前
父节点
当前提交
9f589881cd
共有 3 个文件被更改,包括 148 次插入24 次删除
  1. 8 7
      playground/src/components/TheExperience.vue
  2. 43 17
      src/devtools/plugin.ts
  3. 97 0
      src/utils/index.ts

+ 8 - 7
playground/src/components/TheExperience.vue

@@ -3,6 +3,8 @@ import { ref, watchEffect } from 'vue'
 import { BasicShadowMap, SRGBColorSpace, NoToneMapping } from 'three'
 import { TresCanvas } from '@tresjs/core'
 import { OrbitControls } from '@tresjs/cientos'
+import { TresLeches, useControls } from '@tresjs/leches'
+import '@tresjs/leches/styles'
 import TheSphere from './TheSphere.vue'
 
 const gl = {
@@ -19,6 +21,10 @@ const wireframe = ref(true)
 const canvas = ref()
 const meshRef = ref()
 
+const { isVisible } = useControls({
+  isVisible: true,
+})
+
 watchEffect(() => {
   if (meshRef.value) {
     console.log(meshRef.value)
@@ -27,12 +33,7 @@ watchEffect(() => {
 </script>
 
 <template>
-  <div>
-    <button @click="wireframe = !wireframe">
-      Click
-    </button>
-  </div>
-  <pre>{{ wireframe }}</pre>
+  <TresLeches />
   <TresCanvas
     v-bind="gl"
     ref="canvas"
@@ -75,7 +76,7 @@ watchEffect(() => {
         color="#D3FC8A"
       />
     </TresMesh>
-    <TheSphere />
+    <TheSphere v-if="isVisible" />
     <TresAxesHelper :args="[1]" />
     <TresDirectionalLight
       :position="[0, 2, 4]"

+ 43 - 17
src/devtools/plugin.ts

@@ -4,6 +4,8 @@ import {
   setupDevtoolsPlugin,
 } from '@vue/devtools-api'
 import { reactive } from 'vue'
+import type { Mesh, Object3D } from 'three'
+import { animateHighlight, createHighlightMesh, editSceneObject } from '../utils'
 import { bytesToKB, calculateMemoryUsage } from '../utils/perf'
 import type { TresContext } from '../composables'
 import type { TresObject } from './../types'
@@ -116,6 +118,7 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
     (api) => {
       if (typeof api.now !== 'function') {
         toastMessage(
+          // eslint-disable-next-line max-len
           'You seem to be using an outdated version of Vue Devtools. Are you still using the Beta release instead of the stable one? You can find the links at https://devtools.vuejs.org/guide/installation.html.',
         )
       }
@@ -131,6 +134,10 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
         api.sendInspectorTree(INSPECTOR_ID)
       }, 1000)
 
+      setInterval(() => {
+        api.notifyComponentUpdate()
+      }, 5000)
+
       api.on.getInspectorTree((payload) => {
         if (payload.inspectorId === INSPECTOR_ID) {
           // Your logic here
@@ -164,43 +171,58 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
           ] */
         }
       })
-
+      let highlightMesh: Mesh | null = null
+      let prevInstance: Object3D | null = null
+      
       api.on.getInspectorState((payload) => {
         if (payload.inspectorId === INSPECTOR_ID) {
           // Your logic here
           const [instance] = tres.scene.value.getObjectsByProperty('uuid', payload.nodeId)
+          if (!instance) return 
+          if (prevInstance && highlightMesh && highlightMesh.parent) {
+            prevInstance.remove(highlightMesh)
+          }
+          const newHighlightMesh = createHighlightMesh(instance)
+          instance.add(newHighlightMesh)
+
+          // Start the animation
+          const startTime = Date.now()
+          animateHighlight(newHighlightMesh, startTime)
 
+          highlightMesh = newHighlightMesh
+          prevInstance = instance
+          
           if (instance) {
             payload.state = {
               object: [
                 {
                   key: 'uuid',
-                  editable: false,
+                  editable: true,
                   value: instance.uuid,
                 },
                 {
                   key: 'name',
-                  editable: false,
+                  editable: true,
                   value: instance.name,
                 },
                 {
                   key: 'type',
-                  editable: false,
+                  editable: true,
                   value: instance.type,
                 },
                 {
                   key: 'position',
-                  editable: false,
+                  editable: true,
                   value: instance.position,
                 },
                 {
                   key: 'rotation',
-                  editable: false,
+                  editable: true,
                   value: instance.rotation,
                 },
                 {
                   key: 'scale',
-                  editable: false,
+                  editable: true,
                   value: instance.scale,
                 },
                 {
@@ -213,37 +235,37 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
                 },
                 {
                   key: 'color',
-                  editable: false,
+                  editable: true,
                   value: instance.color,
                 },
                 {
                   key: 'intensity',
-                  editable: false,
+                  editable: true,
                   value: instance.intensity,
                 },
                 {
                   key: 'castShadow',
-                  editable: false,
+                  editable: true,
                   value: instance.castShadow,
                 },
                 {
                   key: 'receiveShadow',
-                  editable: false,
+                  editable: true,
                   value: instance.receiveShadow,
                 },
                 {
                   key: 'frustumCulled',
-                  editable: false,
+                  editable: true,
                   value: instance.frustumCulled,
                 },
                 {
                   key: 'matrixAutoUpdate',
-                  editable: false,
+                  editable: true,
                   value: instance.matrixAutoUpdate,
                 },
                 {
                   key: 'matrixWorldNeedsUpdate',
-                  editable: false,
+                  editable: true,
                   value: instance.matrixWorldNeedsUpdate,
                 },
                 {
@@ -253,9 +275,8 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
                 
                 {
                   key: 'visible',
-                  editable: false,
+                  editable: true,
                   value: instance.visible,
-              
                 },
               ],
             }
@@ -263,7 +284,12 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
           
         }
       })
-   
+
+      api.on.editInspectorState((payload) => {
+        if (payload.inspectorId === INSPECTOR_ID) {
+          editSceneObject(tres.scene.value, payload.nodeId, payload.path, payload.state.value)
+        }
+      })
     },
   )
 }

+ 97 - 0
src/utils/index.ts

@@ -1,3 +1,6 @@
+import { type Scene, type Object3D, Mesh, MeshBasicMaterial, DoubleSide } from 'three'
+import THREE from 'three'
+
 export function toSetMethodName(key: string) {
   return `set${key[0].toUpperCase()}${key.slice(1)}`
 }
@@ -127,3 +130,97 @@ export function deepArrayEqual(arr1: any[], arr2: any[]): boolean {
  * TypeSafe version of Array.isArray
  */
 export const isArray = Array.isArray as (a: any) => a is any[] | readonly any[]
+
+export function editSceneObject(scene: Scene, objectUuid: string, propertyPath: string[], value: any): void {
+  // Function to recursively find the object by UUID
+  const findObjectByUuid = (node: Object3D): Object3D | undefined => {
+    if (node.uuid === objectUuid) {
+      return node
+    }
+
+    for (const child of node.children) {
+      const found = findObjectByUuid(child)
+      if (found) {
+        return found
+      }
+    }
+
+    return undefined
+  }
+
+  // Find the target object
+  const targetObject = findObjectByUuid(scene)
+  if (!targetObject) {
+    console.warn('Object with UUID not found in the scene.')
+    return
+  }
+
+  // Traverse the property path to get to the desired property
+  let currentProperty: any = targetObject
+  for (let i = 0; i < propertyPath.length - 1; i++) {
+    if (currentProperty[propertyPath[i]] !== undefined) {
+      currentProperty = currentProperty[propertyPath[i]]
+    }
+    else {
+      console.warn(`Property path is not valid: ${propertyPath.join('.')}`)
+      return
+    }
+  }
+
+  // Set the new value
+  const lastProperty = propertyPath[propertyPath.length - 1]
+  if (currentProperty[lastProperty] !== undefined) {
+    currentProperty[lastProperty] = value
+  }
+  else {
+    console.warn(`Property path is not valid: ${propertyPath.join('.')}`)
+  }
+}
+
+export function createHighlightMaterial(): MeshBasicMaterial {
+  return new MeshBasicMaterial({
+    color: 0xA7E6D7, // Highlight color, e.g., yellow
+    transparent: true,
+    opacity: 0.2,
+    depthTest: false, // So the highlight is always visible
+    side: DoubleSide, // To ensure the highlight is visible from all angles
+  })
+}
+let animationFrameId: number | null = null
+export function animateHighlight(highlightMesh: Mesh, startTime: number): void {
+  const currentTime = Date.now()
+  const time = (currentTime - startTime) / 1000 // convert to seconds
+
+  // Pulsing effect parameters
+  const scaleAmplitude = 0.07 // Amplitude of the scale pulsation
+  const pulseSpeed = 2.5 // Speed of the pulsation
+
+  // Calculate the scale factor with a sine function for pulsing effect
+  const scaleFactor = 1 + scaleAmplitude * Math.sin(pulseSpeed * time)
+
+  // Apply the scale factor
+  highlightMesh.scale.set(scaleFactor, scaleFactor, scaleFactor)
+
+  // Update the animation frame ID
+  animationFrameId = requestAnimationFrame(() => animateHighlight(highlightMesh, startTime))
+}
+
+export function stopHighlightAnimation(): void {
+  if (animationFrameId !== null) {
+    cancelAnimationFrame(animationFrameId)
+    animationFrameId = null
+  }
+}
+
+export function createHighlightMesh(object: Object3D): Mesh {
+  const highlightMaterial = createHighlightMaterial()
+
+  // Clone the geometry of the object. You might need a more complex approach 
+  // if the object's geometry is not straightforward.
+  const highlightMesh = new Mesh(object.geometry.clone(), highlightMaterial)
+
+  // Scale the highlight mesh slightly larger than the original object
+  highlightMesh.scale.multiplyScalar(1.05)
+
+  return highlightMesh
+}