Browse Source

feat: vue chrome devtools

alvarosabu 1 year ago
parent
commit
a9cc65317f

+ 3 - 3
docs/package.json

@@ -8,11 +8,11 @@
     "build": "vitepress build",
     "preview": "vitepress preview"
   },
+  "dependencies": {
+    "@tresjs/core": "workspace:3.6.0"
+  },
   "devDependencies": {
     "unocss": "^0.58.0",
     "vite-svg-loader": "^5.1.0"
-  },
-  "dependencies": {
-    "@tresjs/core": "workspace:3.6.0-next.0"
   }
 }

+ 11 - 1
playground/.eslintrc.json

@@ -1,7 +1,17 @@
 {
   "extends": "@tresjs/eslint-config-vue",
   "rules": {
-    "no-console": "off"
+    "no-console": "off",
+    "vue/attribute-hyphenation": [2, "always", {
+      "ignore": [
+        "shadow-mapSize-width",
+        "shadow-mapSize-height",
+        "distortionMap",
+        "material-uniforms-mieCoefficient-value",
+        "material-uniforms-mieDirectionalG-value",
+        "material-uniforms-sunPosition-value",
+      ],
+    }],
   }
 }
   

+ 11 - 11
playground/src/components/TheExperience.vue

@@ -17,10 +17,11 @@ const gl = {
 const wireframe = ref(true)
 
 const canvas = ref()
+const meshRef = ref()
 
 watchEffect(() => {
-  if (canvas.value) {
-    console.log(canvas.value.context)
+  if (meshRef.value) {
+    console.log(meshRef.value)
   }
 })
 </script>
@@ -44,14 +45,10 @@ watchEffect(() => {
       :look-at="[0, 4, 0]"
     />
     <OrbitControls />
-    <TresFog
-      :color="gl.clearColor"
-      :near="5"
-      :far="15"
-    />
     <TresMesh
       :position="[-2, 6, 0]"
       :rotation="[0, Math.PI, 0]"
+      name="cone"
       cast-shadow
     >
       <TresConeGeometry :args="[1, 1.5, 3]" />
@@ -68,11 +65,15 @@ watchEffect(() => {
       />
     </TresMesh>
     <TresMesh
-      :rotation="[-Math.PI / 2, 0, 0]"
+      ref="meshRef"
+      :rotation="[-Math.PI / 2, 0, Math.PI / 2]"
+      name="floor"
       receive-shadow
     >
-      <TresPlaneGeometry :args="[10, 10, 10, 10]" />
-      <TresMeshToonMaterial color="#D3FC8A" />
+      <TresPlaneGeometry :args="[20, 20, 20]" />
+      <TresMeshToonMaterial
+        color="#D3FC8A"
+      />
     </TresMesh>
     <TheSphere />
     <TresAxesHelper :args="[1]" />
@@ -81,6 +82,5 @@ watchEffect(() => {
       :intensity="2"
       cast-shadow
     />
-    <TresOrthographicCamera />
   </TresCanvas>
 </template>

+ 1 - 0
playground/src/components/TheSphere.vue

@@ -3,6 +3,7 @@
 <template>
   <TresMesh
     :position="[2, 2, 0]"
+    name="sphere"
     cast-shadow
   >
     <TresSphereGeometry />

+ 6 - 5
pnpm-lock.yaml

@@ -23,7 +23,7 @@ importers:
         version: 1.9.0
       '@tresjs/cientos':
         specifier: 3.6.0
-        version: 3.6.0(@tresjs/core@3.5.1)(three@0.159.0)(tweakpane@4.0.1)(vue@3.3.11)
+        version: 3.6.0(@tresjs/core@)(three@0.159.0)(tweakpane@4.0.1)(vue@3.3.11)
       '@tresjs/eslint-config-vue':
         specifier: ^0.2.1
         version: 0.2.1(@typescript-eslint/eslint-plugin@6.14.0)(eslint@8.55.0)(typescript@5.3.3)
@@ -130,7 +130,7 @@ importers:
   docs:
     dependencies:
       '@tresjs/core':
-        specifier: workspace:3.6.0-next.0
+        specifier: workspace:3.6.0
         version: link:..
     devDependencies:
       unocss:
@@ -144,7 +144,7 @@ importers:
     dependencies:
       '@tresjs/cientos':
         specifier: 3.6.0
-        version: 3.6.0(@tresjs/core@3.5.1)(three@0.159.0)(tweakpane@4.0.1)(vue@3.3.11)
+        version: 3.6.0(@tresjs/core@)(three@0.159.0)(tweakpane@4.0.1)(vue@3.3.11)
       vue-router:
         specifier: ^4.2.5
         version: 4.2.5(vue@3.3.11)
@@ -1812,7 +1812,7 @@ packages:
     resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
     dev: true
 
-  /@tresjs/cientos@3.6.0(@tresjs/core@3.5.1)(three@0.159.0)(tweakpane@4.0.1)(vue@3.3.11):
+  /@tresjs/cientos@3.6.0(@tresjs/core@)(three@0.159.0)(tweakpane@4.0.1)(vue@3.3.11):
     resolution: {integrity: sha512-VM6LamAFlcKufbrtbYN71ncuAw2JPVfKUC6Ey9+scq05qvHdQM8fU0WoppNZEtmIL7m2aUqroOZRnr9LXyZPCg==}
     peerDependencies:
       '@tresjs/core': '>=3.2'
@@ -1820,7 +1820,7 @@ packages:
       tweakpane: '>=3.0.0'
       vue: '>=3.3'
     dependencies:
-      '@tresjs/core': 3.5.1(three@0.159.0)(vue@3.3.11)
+      '@tresjs/core': 'link:'
       '@vueuse/core': 10.7.0(vue@3.3.11)
       camera-controls: 2.7.3(three@0.159.0)
       stats-gl: 1.0.7
@@ -1847,6 +1847,7 @@ packages:
       vue: 3.3.11(typescript@5.3.3)
     transitivePeerDependencies:
       - '@vue/composition-api'
+    dev: true
 
   /@tresjs/eslint-config-base@0.2.1(@typescript-eslint/eslint-plugin@6.14.0)(@typescript-eslint/parser@6.14.0)(eslint@8.55.0):
     resolution: {integrity: sha512-9fkwDaNu4nLKujeERi5d1S7+ZdZpxBE+g/jUbM4ywhn/+5P7Qv8dXlo1vB05LteX5cTBnZxHQTFrJGK+sMcFdg==}

+ 5 - 0
src/components/TresCanvas.vue

@@ -33,6 +33,7 @@ import { render } from '../core/renderer'
 
 import type { RendererPresetsType } from '../composables/useRenderer/const'
 import type { TresCamera, TresObject } from '../types/'
+import { registerTresDevtools } from '../devtools'
 
 export interface TresCanvasProps
   extends Omit<WebGLRendererParameters, 'canvas'> {
@@ -92,6 +93,10 @@ const createInternalComponent = (context: TresContext) =>
       if (ctx) ctx.app = instance as App
       provide('useTres', context)
       provide('extend', extend)
+
+      if (typeof window !== 'undefined') {
+        registerTresDevtools(ctx.app, context)
+      }
       return () => h(Fragment, null, slots?.default ? slots.default() : [])
     },
   })

+ 1 - 0
src/devtools/index.ts

@@ -0,0 +1 @@
+export { registerTresDevtools } from './plugin'

+ 269 - 0
src/devtools/plugin.ts

@@ -0,0 +1,269 @@
+import type {
+  App as DevtoolsApp } from '@vue/devtools-api'
+import {
+  setupDevtoolsPlugin,
+} from '@vue/devtools-api'
+import { reactive } from 'vue'
+import { bytesToKB, calculateMemoryUsage } from '../utils/perf'
+import type { TresContext } from '../composables'
+import type { TresObject } from './../types'
+import { toastMessage } from './utils'
+
+export interface Tags {
+  label: string
+  textColor: number
+  backgroundColor: number
+  tooltip?: string
+}
+
+export interface SceneGraphObject {
+  id: string
+  label: string
+  icon: string
+  children: SceneGraphObject[]
+  tags: Tags[]
+}
+
+const createNode = (object: TresObject): SceneGraphObject => {
+  const node: SceneGraphObject = {
+    id: object.uuid,
+    label: object.type,
+    children: [],
+    tags: [],
+  }
+  if (object.name !== '') {
+    node.tags.push({
+      label: object.name,
+      textColor: 0x57BF65,
+      backgroundColor: 0xF0FCF3,
+    })
+  }
+  const memory = calculateMemoryUsage(object)
+  if (memory > 0) {
+    node.tags.push({
+      label: `${bytesToKB(memory)} KB`,
+      textColor: 0xEFAC35,
+      backgroundColor: 0xFFF9DC,
+      tooltip: 'Memory usage',
+    })
+  }
+
+  if (object.type.includes('Light')) {
+    node.tags.push({
+      label: `${object.intensity}`,
+      textColor: 0x9499A6,
+      backgroundColor: 0xF8F9FA,
+      tooltip: 'Intensity',
+    })
+    node.tags.push({
+      label: `#${object.color.getHexString()}`,
+      textColor: 0x9499A6,
+      backgroundColor: 0xF8F9FA,
+      tooltip: 'Color',
+    })
+  }
+
+  if (object.type.includes('Camera')) {
+    node.tags.push({
+      label: `${object.fov}°`,
+      textColor: 0x9499A6,
+      backgroundColor: 0xF8F9FA,
+      tooltip: 'Field of view',
+    })
+    node.tags.push({
+      label: `x: ${Math.round(object.position.x)} y: ${Math.round(object.position.y)} z: ${Math.round(object.position.z)}`,
+      textColor: 0x9499A6,
+      backgroundColor: 0xF8F9FA,
+      tooltip: 'Position',
+    })
+  }
+  /* if (object.position) {
+    node.tags.push({
+      label: `x: ${object.position.x} y: ${object.position.y} z: ${object.position.z}`,
+      textColor: 0x9499A6,
+      backgroundColor: 0xF8F9FA,
+      tooltip: 'Position',
+    })
+  } */
+  return node
+}
+
+function buildGraph(object: TresObject, node: SceneGraphObject) {
+  object.children.forEach((child: TresObject) => {
+    const childNode = createNode(child)
+    node.children.push(childNode)
+    buildGraph(child, childNode)
+  })
+}
+
+const componentStateTypes: string[] = []
+const INSPECTOR_ID = 'tres:inspector'
+
+const state = reactive({
+  sceneGraph: null as SceneGraphObject | null,
+})
+export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
+  setupDevtoolsPlugin(
+    {
+      id: 'dev.esm.tres',
+      label: 'TresJS 🪐',
+      logo: 'https://raw.githubusercontent.com/Tresjs/tres/main/public/favicon.svg',
+      packageName: 'tresjs',
+      homepage: 'https://tresjs.org',
+      componentStateTypes,
+      app,
+    },
+    (api) => {
+      if (typeof api.now !== 'function') {
+        toastMessage(
+          '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.',
+        )
+      }
+
+      api.addInspector({
+        id: INSPECTOR_ID,
+        label: 'TresJS 🪐',
+        icon: 'account_tree',
+        treeFilterPlaceholder: 'Search instances',
+      })
+
+      setInterval(() => {
+        api.sendInspectorTree(INSPECTOR_ID)
+      }, 1000)
+
+      api.on.getInspectorTree((payload) => {
+        if (payload.inspectorId === INSPECTOR_ID) {
+          // Your logic here
+          const root = createNode(tres.scene.value)
+          buildGraph(tres.scene.value, root)
+          state.sceneGraph = root
+          payload.rootNodes = [root]
+          /*  payload.rootNodes = [
+            {
+              id: 'root',
+              label: 'Root ',
+              children: [
+                {
+                  id: 'child',
+                  label: `Child ${payload.filter}`,
+                  tags: [
+                    {
+                      label: 'active',
+                      textColor: 0x000000,
+                      backgroundColor: 0xFF984F,
+                    },
+                    {
+                      label: 'test',
+                      textColor: 0xffffff,
+                      backgroundColor: 0x000000,
+                    },
+                  ],
+                },
+              ],
+            },
+          ] */
+        }
+      })
+
+      api.on.getInspectorState((payload) => {
+        if (payload.inspectorId === INSPECTOR_ID) {
+          // Your logic here
+          const [instance] = tres.scene.value.getObjectsByProperty('uuid', payload.nodeId)
+
+          if (instance) {
+            payload.state = {
+              object: [
+                {
+                  key: 'uuid',
+                  editable: false,
+                  value: instance.uuid,
+                },
+                {
+                  key: 'name',
+                  editable: false,
+                  value: instance.name,
+                },
+                {
+                  key: 'type',
+                  editable: false,
+                  value: instance.type,
+                },
+                {
+                  key: 'position',
+                  editable: false,
+                  value: instance.position,
+                },
+                {
+                  key: 'rotation',
+                  editable: false,
+                  value: instance.rotation,
+                },
+                {
+                  key: 'scale',
+                  editable: false,
+                  value: instance.scale,
+                },
+                {
+                  key: 'geometry',
+                  value: instance.geometry,
+                },
+                {
+                  key: 'material',
+                  value: instance.material,
+                },
+                {
+                  key: 'color',
+                  editable: false,
+                  value: instance.color,
+                },
+                {
+                  key: 'intensity',
+                  editable: false,
+                  value: instance.intensity,
+                },
+                {
+                  key: 'castShadow',
+                  editable: false,
+                  value: instance.castShadow,
+                },
+                {
+                  key: 'receiveShadow',
+                  editable: false,
+                  value: instance.receiveShadow,
+                },
+                {
+                  key: 'frustumCulled',
+                  editable: false,
+                  value: instance.frustumCulled,
+                },
+                {
+                  key: 'matrixAutoUpdate',
+                  editable: false,
+                  value: instance.matrixAutoUpdate,
+                },
+                {
+                  key: 'matrixWorldNeedsUpdate',
+                  editable: false,
+                  value: instance.matrixWorldNeedsUpdate,
+                },
+                {
+                  key: 'matrixWorld',
+                  value: instance.matrixWorld,
+                },
+                
+                {
+                  key: 'visible',
+                  editable: false,
+                  value: instance.visible,
+              
+                },
+              ],
+            }
+          }
+          
+        }
+      })
+   
+    },
+  )
+}

+ 26 - 0
src/devtools/utils.ts

@@ -0,0 +1,26 @@
+/**
+ * Shows a toast or console.log
+ *
+ * @param message - message to log
+ * @param type - different color of the tooltip
+ */
+export function toastMessage(
+  message: string,
+  type?: 'normal' | 'error' | 'warn' | undefined,
+) {
+  const tresMessage = `▲ ■ ●${message}`
+
+  if (typeof __VUE_DEVTOOLS_TOAST__ === 'function') {
+    // No longer available :(
+    __VUE_DEVTOOLS_TOAST__(tresMessage, type)
+  }
+  else if (type === 'error') {
+    console.error(tresMessage)
+  }
+  else if (type === 'warn') {
+    console.warn(tresMessage)
+  }
+  else {
+    console.log(tresMessage)
+  }
+}

+ 1 - 1
src/utils/perf.ts

@@ -18,7 +18,7 @@ export function calculateMemoryUsage(object: TresObject | Scene) {
     }
   })
 
-  return totalMemory
+  return totalMemory // In bytes
 }
 
 export function bytesToKB(bytes: number): string {