浏览代码

feat: editable scene graph

alvarosabu 1 年之前
父节点
当前提交
dbecefb53e

+ 12 - 4
plugins/vite-plugin-tres/client/.nuxt/components.d.ts

@@ -8,10 +8,12 @@ declare module 'vue' {
     'PerformanceMonitor': typeof import("../components/PerformanceMonitor.vue")['default']
     'PerformanceMonitor': typeof import("../components/PerformanceMonitor.vue")['default']
     'SceneGraphItem': typeof import("../components/SceneGraphItem.vue")['default']
     'SceneGraphItem': typeof import("../components/SceneGraphItem.vue")['default']
     'TheHeader': typeof import("../components/TheHeader.vue")['default']
     'TheHeader': typeof import("../components/TheHeader.vue")['default']
+    'InspectorBoolean': typeof import("../components/inspector/InspectorBoolean.vue")['default']
     'InspectorBranch': typeof import("../components/inspector/InspectorBranch.vue")['default']
     'InspectorBranch': typeof import("../components/inspector/InspectorBranch.vue")['default']
+    'InspectorControl': typeof import("../components/inspector/InspectorControl.vue")['default']
+    'InspectorNumber': typeof import("../components/inspector/InspectorNumber.vue")['default']
     'InspectorState': typeof import("../components/inspector/InspectorState.vue")['default']
     'InspectorState': typeof import("../components/inspector/InspectorState.vue")['default']
     'InspectorTree': typeof import("../components/inspector/InspectorTree.vue")['default']
     'InspectorTree': typeof import("../components/inspector/InspectorTree.vue")['default']
-    'InspectorProps': typeof import("../components/inspector/props")['default']
     'ProgramsModule': typeof import("../components/programs-module/index.vue")['default']
     'ProgramsModule': typeof import("../components/programs-module/index.vue")['default']
     'ProgramsModuleItem': typeof import("../components/programs-module/item.vue")['default']
     'ProgramsModuleItem': typeof import("../components/programs-module/item.vue")['default']
     'UnoIcon': typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
     'UnoIcon': typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
@@ -114,10 +116,12 @@ declare module 'vue' {
     'LazyPerformanceMonitor': typeof import("../components/PerformanceMonitor.vue")['default']
     'LazyPerformanceMonitor': typeof import("../components/PerformanceMonitor.vue")['default']
     'LazySceneGraphItem': typeof import("../components/SceneGraphItem.vue")['default']
     'LazySceneGraphItem': typeof import("../components/SceneGraphItem.vue")['default']
     'LazyTheHeader': typeof import("../components/TheHeader.vue")['default']
     'LazyTheHeader': typeof import("../components/TheHeader.vue")['default']
+    'LazyInspectorBoolean': typeof import("../components/inspector/InspectorBoolean.vue")['default']
     'LazyInspectorBranch': typeof import("../components/inspector/InspectorBranch.vue")['default']
     'LazyInspectorBranch': typeof import("../components/inspector/InspectorBranch.vue")['default']
+    'LazyInspectorControl': typeof import("../components/inspector/InspectorControl.vue")['default']
+    'LazyInspectorNumber': typeof import("../components/inspector/InspectorNumber.vue")['default']
     'LazyInspectorState': typeof import("../components/inspector/InspectorState.vue")['default']
     'LazyInspectorState': typeof import("../components/inspector/InspectorState.vue")['default']
     'LazyInspectorTree': typeof import("../components/inspector/InspectorTree.vue")['default']
     'LazyInspectorTree': typeof import("../components/inspector/InspectorTree.vue")['default']
-    'LazyInspectorProps': typeof import("../components/inspector/props")['default']
     'LazyProgramsModule': typeof import("../components/programs-module/index.vue")['default']
     'LazyProgramsModule': typeof import("../components/programs-module/index.vue")['default']
     'LazyProgramsModuleItem': typeof import("../components/programs-module/item.vue")['default']
     'LazyProgramsModuleItem': typeof import("../components/programs-module/item.vue")['default']
     'LazyUnoIcon': typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
     'LazyUnoIcon': typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
@@ -223,10 +227,12 @@ export const Pane: typeof import("../components/Pane.vue")['default']
 export const PerformanceMonitor: typeof import("../components/PerformanceMonitor.vue")['default']
 export const PerformanceMonitor: typeof import("../components/PerformanceMonitor.vue")['default']
 export const SceneGraphItem: typeof import("../components/SceneGraphItem.vue")['default']
 export const SceneGraphItem: typeof import("../components/SceneGraphItem.vue")['default']
 export const TheHeader: typeof import("../components/TheHeader.vue")['default']
 export const TheHeader: typeof import("../components/TheHeader.vue")['default']
+export const InspectorBoolean: typeof import("../components/inspector/InspectorBoolean.vue")['default']
 export const InspectorBranch: typeof import("../components/inspector/InspectorBranch.vue")['default']
 export const InspectorBranch: typeof import("../components/inspector/InspectorBranch.vue")['default']
+export const InspectorControl: typeof import("../components/inspector/InspectorControl.vue")['default']
+export const InspectorNumber: typeof import("../components/inspector/InspectorNumber.vue")['default']
 export const InspectorState: typeof import("../components/inspector/InspectorState.vue")['default']
 export const InspectorState: typeof import("../components/inspector/InspectorState.vue")['default']
 export const InspectorTree: typeof import("../components/inspector/InspectorTree.vue")['default']
 export const InspectorTree: typeof import("../components/inspector/InspectorTree.vue")['default']
-export const InspectorProps: typeof import("../components/inspector/props")['default']
 export const ProgramsModule: typeof import("../components/programs-module/index.vue")['default']
 export const ProgramsModule: typeof import("../components/programs-module/index.vue")['default']
 export const ProgramsModuleItem: typeof import("../components/programs-module/item.vue")['default']
 export const ProgramsModuleItem: typeof import("../components/programs-module/item.vue")['default']
 export const UnoIcon: typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
 export const UnoIcon: typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
@@ -329,10 +335,12 @@ export const LazyPane: typeof import("../components/Pane.vue")['default']
 export const LazyPerformanceMonitor: typeof import("../components/PerformanceMonitor.vue")['default']
 export const LazyPerformanceMonitor: typeof import("../components/PerformanceMonitor.vue")['default']
 export const LazySceneGraphItem: typeof import("../components/SceneGraphItem.vue")['default']
 export const LazySceneGraphItem: typeof import("../components/SceneGraphItem.vue")['default']
 export const LazyTheHeader: typeof import("../components/TheHeader.vue")['default']
 export const LazyTheHeader: typeof import("../components/TheHeader.vue")['default']
+export const LazyInspectorBoolean: typeof import("../components/inspector/InspectorBoolean.vue")['default']
 export const LazyInspectorBranch: typeof import("../components/inspector/InspectorBranch.vue")['default']
 export const LazyInspectorBranch: typeof import("../components/inspector/InspectorBranch.vue")['default']
+export const LazyInspectorControl: typeof import("../components/inspector/InspectorControl.vue")['default']
+export const LazyInspectorNumber: typeof import("../components/inspector/InspectorNumber.vue")['default']
 export const LazyInspectorState: typeof import("../components/inspector/InspectorState.vue")['default']
 export const LazyInspectorState: typeof import("../components/inspector/InspectorState.vue")['default']
 export const LazyInspectorTree: typeof import("../components/inspector/InspectorTree.vue")['default']
 export const LazyInspectorTree: typeof import("../components/inspector/InspectorTree.vue")['default']
-export const LazyInspectorProps: typeof import("../components/inspector/props")['default']
 export const LazyProgramsModule: typeof import("../components/programs-module/index.vue")['default']
 export const LazyProgramsModule: typeof import("../components/programs-module/index.vue")['default']
 export const LazyProgramsModuleItem: typeof import("../components/programs-module/item.vue")['default']
 export const LazyProgramsModuleItem: typeof import("../components/programs-module/item.vue")['default']
 export const LazyUnoIcon: typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']
 export const LazyUnoIcon: typeof import("../../../../node_modules/.pnpm/@unocss+nuxt@0.58.3_postcss@8.4.33_rollup@3.29.4_vite@5.0.11_webpack@5.89.0/node_modules/@unocss/nuxt/runtime/UnoIcon.vue")['default']

+ 1 - 1
plugins/vite-plugin-tres/client/.nuxt/manifest/latest.json

@@ -1 +1 @@
-{"id":"dev","timestamp":1705252415652}
+{"id":"dev","timestamp":1705663218171}

+ 1 - 1
plugins/vite-plugin-tres/client/.nuxt/manifest/meta/dev.json

@@ -1 +1 @@
-{"id":"dev","timestamp":1705252415652,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
+{"id":"dev","timestamp":1705663218171,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}

+ 3 - 3
plugins/vite-plugin-tres/client/.nuxt/nitro.json

@@ -1,5 +1,5 @@
 {
 {
-  "date": "2024-01-14T17:13:45.236Z",
+  "date": "2024-01-19T11:20:24.326Z",
   "preset": "nitro-dev",
   "preset": "nitro-dev",
   "framework": {
   "framework": {
     "name": "nuxt",
     "name": "nuxt",
@@ -9,9 +9,9 @@
     "nitro": "2.8.1"
     "nitro": "2.8.1"
   },
   },
   "dev": {
   "dev": {
-    "pid": 89321,
+    "pid": 54402,
     "workerAddress": {
     "workerAddress": {
-      "socketPath": "/var/folders/66/14k3nnbx1g505216sq4xdfdc0000gn/T/nitro/worker-89321-2.sock"
+      "socketPath": "/var/folders/66/14k3nnbx1g505216sq4xdfdc0000gn/T/nitro/worker-54402-2.sock"
     }
     }
   }
   }
 }
 }

+ 72 - 0
plugins/vite-plugin-tres/client/components/inspector/InspectorBoolean.vue

@@ -0,0 +1,72 @@
+<script setup lang="ts">
+const props = defineProps<{
+  label: string
+  value: any
+  type?: string
+  path?: string
+}>()
+
+const emit = defineEmits(['change'])
+
+function onChange(event: Event) {
+  const { target } = event
+  emit('change', (target as HTMLInputElement).checked)
+}
+
+function onKeydown(event: KeyboardEvent) {
+  if (event.code === 'Space' || event.code === 'Enter') {
+    event.preventDefault() // Prevent scrolling when space is pressed
+    emit('change', !props.value)
+  }
+}
+</script>
+
+<template>
+  <div class="flex px-4 justify-start gap-2 items-center">
+    <UBadge
+      color="gray"
+      variant="soft"
+    > 
+      {{ value }}
+    </UBadge> 
+    <input
+      :id="path"
+      :checked="value"
+      class="hidden"
+      type="checkbox"
+      @input="onChange"
+    >
+    <label
+      :for="path"
+      class="inline-flex items-center cursor-pointer"
+    >
+      <span
+        tabindex="0"
+        role="checkbox"
+        :aria-checked="value.toString()"
+        :class="{
+          'bg-dark-500': value, 
+          'bg-gray-200': !value, 
+        }"
+        class="w-4 
+          h-4 
+          flex 
+          justify-center 
+          items-center 
+          rounded 
+          border 
+          border-gray-300 
+          mr-2 
+          transition-colors 
+          duration-200
+          dark:bg-slate-400/50
+        "
+        @keydown="onKeydown"
+      >
+        <i
+          v-show="value"
+          class="i-carbon-checkmark text-light"
+        /></span>
+    </label>
+  </div>
+</template>

+ 10 - 7
plugins/vite-plugin-tres/client/components/inspector/InspectorBranch.vue

@@ -4,6 +4,7 @@ import { toRaw } from 'vue'
 const props = defineProps<{
 const props = defineProps<{
   entry: any
   entry: any
   depth?: number
   depth?: number
+  path?: string
 }>()
 }>()
 
 
 const isObject = value => value && typeof value === 'object' && !Array.isArray(value)
 const isObject = value => value && typeof value === 'object' && !Array.isArray(value)
@@ -67,6 +68,7 @@ function changeValue(key, value) {
             :key="item.key"
             :key="item.key"
             :entry="{ ...item.value }"
             :entry="{ ...item.value }"
             :depth="depth + 1"
             :depth="depth + 1"
+            :path="path ? `${path}.${item.key}` : item.key"
           />
           />
         </template>
         </template>
         <!-- Handle Arrays -->
         <!-- Handle Arrays -->
@@ -104,17 +106,18 @@ function changeValue(key, value) {
           for=""
           for=""
           class="text-gray-400"
           class="text-gray-400"
         >{{ item.key }}</label> :
         >{{ item.key }}</label> :
-        <UBadge
+        <!-- <UBadge
           color="gray"
           color="gray"
           variant="soft"
           variant="soft"
         > 
         > 
           {{ isString(item.value) ? '"' : '' }}  {{ item.value }}   {{ isString(item.value) ? '"' : '' }}
           {{ isString(item.value) ? '"' : '' }}  {{ item.value }}   {{ isString(item.value) ? '"' : '' }}
-        </UBadge>
-        <!-- <input
-          :value="toRaw(item.value)"
-          type="text"
-          @input="changeValue(item.key, $event.target.value)"
-        > -->
+        </UBadge> -->
+        <InspectorControl
+          :label="item.key"
+          :value="item.value"
+          :path="path ? `${path}.${item.key}` : item.key"
+          :type="typeof item.value"
+        />
       </div>
       </div>
     </template>
     </template>
   </div>
   </div>

+ 45 - 0
plugins/vite-plugin-tres/client/components/inspector/InspectorControl.vue

@@ -0,0 +1,45 @@
+<script setup lang="ts">
+import { useDevtoolsHook } from '../../composables/useDevtoolsHook'
+import InspectorBoolean from './InspectorBoolean.vue'
+import InspectorNumber from './InspectorNumber.vue'
+
+const props = defineProps<{
+  label: string
+  value: any
+  type?: string
+  path?: string
+}>()
+
+const { editSceneObjectByPath } = useDevtoolsHook()
+
+function changeValue(value) {
+  editSceneObjectByPath(props?.path || '', value)
+}
+</script>
+
+<template>
+  <div class="flex items-center">
+    <InspectorBoolean
+      v-if="type === 'boolean'" 
+      :label="label"
+      :value="value"
+      :type="type"
+      :path="path"
+      @change="changeValue"
+    />
+    <InspectorNumber
+      v-else-if="type === 'number'"
+      :label="label"
+      :value="value"
+      :type="type"
+      :path="path"
+      @change="changeValue"
+    />
+    <!-- <input
+      class="ml-2 bg-gray-200"
+      :value="value"
+      :type="type"
+      @input="changeValue"
+    > -->
+  </div>
+</template>

+ 96 - 0
plugins/vite-plugin-tres/client/components/inspector/InspectorNumber.vue

@@ -0,0 +1,96 @@
+<script setup lang="ts">
+import { watch } from 'vue'
+
+const props = defineProps<{
+  label: string
+  value: any
+  type?: string
+  path?: string
+  min?: number
+  max?: number
+  step?: number
+}>()
+
+const emit = defineEmits(['change'])
+
+function onChange(event: Event) {
+  const { target } = event
+  emit('change', (target as HTMLInputElement).valueAsNumber)
+}
+
+function onKeydown(event: KeyboardEvent) {
+  if (event.code === 'Space' || event.code === 'Enter') {
+    event.preventDefault() // Prevent scrolling when space is pressed
+    emit('change', !props.value)
+  }
+}
+
+const mouse = useMouse()
+
+const initialMouseX = ref(0)
+const isMouseDown = ref(false)
+
+const onInputMouseDown = (event: MouseEvent) => {
+  initialMouseX.value = event.clientX
+  isMouseDown.value = true
+}
+
+const onInputMouseUp = () => {
+  isMouseDown.value = false
+}
+
+const calculateSpeed = (diff: number) => Math.floor(Math.abs(diff) / 10)
+
+watch(mouse.x, (newValue) => {
+  if (isMouseDown.value) {
+    const diff = newValue - initialMouseX.value
+    const speed = calculateSpeed(diff)
+    if (diff > 0) {
+      emit('change', props.value + 1 + speed)
+    }
+    else if (diff < 0) {
+      emit('change', props.value - 1 + speed)
+    }
+
+    if (props.min !== undefined && props.value < props.min) {
+      emit('change', props.min)
+    }
+
+    if (props.max !== undefined && props.value > props.max) {
+      emit('change', props.max)
+    }
+
+    initialMouseX.value = newValue
+  }
+})
+</script>
+
+<template>
+  <div class="flex justify-start gap-2 items-center">
+    <input
+      :id="path"
+      :value="value"
+      :min="min"
+      :max="max"
+      :step="step"
+      class="
+        bg-gray-50
+        px-2 py-1
+        text-xs 
+        font-mono
+        rounded
+        text-right
+        focus:border-gray-200
+        outline-none
+        border-none
+        w-1/3
+        w-max-content
+      "
+      type="number"
+      :class="{ 'cursor-ew-resize': isMouseDown }"
+      @input="onChange"
+      @mousedown="onInputMouseDown"
+      @mouseup="onInputMouseUp"
+    >
+  </div>
+</template>

+ 0 - 6
plugins/vite-plugin-tres/client/components/inspector/props.js

@@ -1,6 +0,0 @@
-import { defineProps } from 'vue';
-
-export const props = defineProps < {
-label: string,
-value: any
-} > ();

+ 7 - 0
plugins/vite-plugin-tres/client/composables/useDevtoolsHook.ts

@@ -3,6 +3,7 @@ import { computed, nextTick, reactive, ref, toRefs } from 'vue'
 import type { DevtoolsContextPayload, DevtoolsPerformancePayload, DevtoolsEvent, TresObject } from '@tresjs/core'
 import type { DevtoolsContextPayload, DevtoolsPerformancePayload, DevtoolsEvent, TresObject } from '@tresjs/core'
 import type { Mesh, Object3D } from 'three'
 import type { Mesh, Object3D } from 'three'
 import { DoubleSide, MeshBasicMaterial } from 'three'
 import { DoubleSide, MeshBasicMaterial } from 'three'
+import { editSceneObject } from '@tresjs/core'
 import type { GraphObject } from '../types'
 import type { GraphObject } from '../types'
 import { HightlightMesh } from '../utils/highlightedMesh'
 import { HightlightMesh } from '../utils/highlightedMesh'
 
 
@@ -20,6 +21,7 @@ interface DevtoolsHookReturn {
   highlightObject: (object: TresObject) => void
   highlightObject: (object: TresObject) => void
   unhighlightObject: () => void
   unhighlightObject: () => void
   selectObject: (object: TresObject) => void
   selectObject: (object: TresObject) => void
+  editSceneObjectByPath: (path: string, value: any) => void
 }
 }
 
 
 const state = reactive<DevtoolsState>({
 const state = reactive<DevtoolsState>({
@@ -163,6 +165,10 @@ function selectObject(object: TresObject) {
   state.selectedObject = { ...instance }
   state.selectedObject = { ...instance }
 }
 }
 
 
+function editSceneObjectByPath(path: string, value: any) {
+  editSceneObject(state.scene, state.selectedObject.uuid, path.split('.'), value)
+}
+
 export function useDevtoolsHook(): DevtoolsHookReturn {
 export function useDevtoolsHook(): DevtoolsHookReturn {
   function cb(event: DevtoolsEvent) {
   function cb(event: DevtoolsEvent) {
     state.connected = true
     state.connected = true
@@ -191,5 +197,6 @@ export function useDevtoolsHook(): DevtoolsHookReturn {
     highlightObject,
     highlightObject,
     unhighlightObject,
     unhighlightObject,
     selectObject,
     selectObject,
+    editSceneObjectByPath,
   }
   }
 } 
 } 

+ 2 - 0
src/index.ts

@@ -2,6 +2,7 @@ import type { App } from 'vue'
 import TresCanvas from './components/TresCanvas.vue'
 import TresCanvas from './components/TresCanvas.vue'
 import { normalizeColor, normalizeVectorFlexibleParam } from './utils/normalize'
 import { normalizeColor, normalizeVectorFlexibleParam } from './utils/normalize'
 import templateCompilerOptions from './utils/template-compiler-options'
 import templateCompilerOptions from './utils/template-compiler-options'
+import { editSceneObject } from './utils'
 
 
 export * from './composables'
 export * from './composables'
 export * from './core/catalogue'
 export * from './core/catalogue'
@@ -29,4 +30,5 @@ export {
   normalizeColor,
   normalizeColor,
   normalizeVectorFlexibleParam,
   normalizeVectorFlexibleParam,
   templateCompilerOptions,
   templateCompilerOptions,
+  editSceneObject,
 }
 }