1
0

useDevtoolsHook.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import type { ComputedRef } from 'vue'
  2. import { computed, nextTick, reactive, ref, toRefs } from 'vue'
  3. import type { DevtoolsContextPayload, DevtoolsPerformancePayload, DevtoolsEvent, TresObject } from '@tresjs/core'
  4. import type { Mesh, Object3D } from 'three'
  5. import { DoubleSide, MeshBasicMaterial } from 'three'
  6. import { editSceneObject } from '@tresjs/core'
  7. import type { GraphObject } from '../types'
  8. import { HightlightMesh } from '../utils/highlightedMesh'
  9. interface DevtoolsState {
  10. connected: boolean
  11. scene: Object3D
  12. selectedObject: Object3D | null
  13. prevInstance: Object3D | null
  14. highlightMesh: Mesh | null
  15. invalidate: (frames?: number) => void
  16. }
  17. interface DevtoolsHookReturn {
  18. state: DevtoolsState
  19. graph: ComputedRef<GraphObject>
  20. highlightObject: (object: TresObject) => void
  21. unhighlightObject: () => void
  22. selectObject: (object: TresObject) => void
  23. editSceneObjectByPath: (path: string, value: any) => void
  24. }
  25. const state = reactive<DevtoolsState>({
  26. connected: false,
  27. scene: {} as Object3D,
  28. selectedObject: null as Object3D | null,
  29. prevInstance: null as Object3D | null,
  30. highlightMesh: null as Mesh | null,
  31. invalidate: () => {},
  32. })
  33. function handlePerformanceEvent(payload: DevtoolsPerformancePayload) {
  34. }
  35. function handleContextEvent(payload: DevtoolsContextPayload) {
  36. payload.scene.traverse((children) => {
  37. if (payload.scene.__vnode) delete children.__vnode
  38. delete children.userData.tres__context
  39. delete children.userData.tres__root
  40. })
  41. const { _vnode, ...rest } = payload.scene
  42. state.scene = {
  43. ...rest,
  44. getObjectByProperty: payload.scene.getObjectByProperty,
  45. traverse: payload.scene.traverse,
  46. }
  47. if (state.selectedObject) {
  48. selectObject(state.selectedObject)
  49. }
  50. // On-demand rendering
  51. state.invalidate = payload.invalidate
  52. }
  53. const icons: Record<string, string> = {
  54. scene: 'i-carbon-web-services-container',
  55. perspectivecamera: 'i-carbon-video',
  56. mesh: 'i-carbon-cube',
  57. group: 'i-carbon-group-objects',
  58. ambientlight: 'i-carbon-light',
  59. directionallight: 'i-carbon-light',
  60. spotlight: 'i-iconoir-project-curve-3d',
  61. position: 'i-iconoir-axes',
  62. rotation: 'i-carbon-rotate-clockwise',
  63. scale: 'i-iconoir-ellipse-3d-three-points',
  64. bone: 'i-ph-bone',
  65. skinnedmesh: 'carbon:3d-print-mesh',
  66. }
  67. function createNode(object: TresObject) {
  68. const node: GraphObject = {
  69. uuid: object.uuid,
  70. name: object.name,
  71. type: object.type,
  72. icon: icons[object.type.toLowerCase()] || 'i-carbon-cube',
  73. position: {
  74. x: object.position.x,
  75. y: object.position.y,
  76. z: object.position.z,
  77. },
  78. rotation: {
  79. x: object.rotation.x,
  80. y: object.rotation.y,
  81. z: object.rotation.z,
  82. },
  83. children: [],
  84. }
  85. if (object.type === 'Mesh') {
  86. node.material = object.material
  87. node.geometry = object.geometry
  88. node.scale = {
  89. x: object.scale.x,
  90. y: object.scale.y,
  91. z: object.scale.z,
  92. }
  93. }
  94. if (object.type.includes('Light')) {
  95. node.color = object.color.getHexString()
  96. node.intensity = object.intensity
  97. }
  98. return node
  99. }
  100. const graph = computed(() => {
  101. const traverse = (object: TresObject, node: GraphObject) => {
  102. object.children.forEach((child) => {
  103. const childNode = createNode(child)
  104. if (child.type !== 'HightlightMesh') {
  105. node.children.push(childNode)
  106. }
  107. traverse(child, childNode)
  108. })
  109. }
  110. const root = createNode(state.scene)
  111. traverse(state.scene, root)
  112. return root
  113. })
  114. function createHighlightMesh(object: Object3D): Mesh {
  115. const highlightMaterial = new MeshBasicMaterial({
  116. color: 0xa7e6d7, // Highlight color
  117. transparent: true,
  118. opacity: 0.2,
  119. depthTest: false, // So the highlight is always visible
  120. side: DoubleSide, // To e
  121. })
  122. // Clone the geometry of the object. You might need a more complex approach
  123. // if the object's geometry is not straightforward.
  124. const highlightMesh = new HightlightMesh(state.invalidate, object.geometry.clone(), highlightMaterial)
  125. return highlightMesh
  126. }
  127. function highlightObject(object: TresObject) {
  128. const instance = state.scene.getObjectByProperty('uuid', object.uuid)
  129. unhighlightObject()
  130. if (instance && instance.isMesh) {
  131. const newHighlightMesh = createHighlightMesh(instance)
  132. instance.add(newHighlightMesh)
  133. state.highlightMesh = newHighlightMesh
  134. state.prevInstance = instance
  135. }
  136. }
  137. function unhighlightObject() {
  138. if (state.prevInstance && state.highlightMesh && state.highlightMesh.parent) {
  139. state.prevInstance.remove(state.highlightMesh)
  140. }
  141. }
  142. function selectObject(object: TresObject) {
  143. const instance = state.scene.getObjectByProperty('uuid', object.uuid)
  144. state.selectedObject = {}
  145. state.selectedObject = { ...instance }
  146. }
  147. function editSceneObjectByPath(path: string, value: any) {
  148. editSceneObject(state.scene, state.selectedObject.uuid, path.split('.'), value)
  149. }
  150. export function useDevtoolsHook(): DevtoolsHookReturn {
  151. function cb(event: DevtoolsEvent) {
  152. state.connected = true
  153. if (event.type === 'performance') {
  154. handlePerformanceEvent(event.payload)
  155. }
  156. else if (event.type === 'context') {
  157. handleContextEvent(event.payload)
  158. }
  159. }
  160. // Init devtools hook
  161. const tresGlobalHook = {
  162. cb,
  163. }
  164. if (!window.parent.parent.__TRES__DEVTOOLS__) {
  165. window.parent.parent.__TRES__DEVTOOLS__ = tresGlobalHook
  166. }
  167. return {
  168. state,
  169. graph,
  170. ...toRefs(state),
  171. highlightObject,
  172. unhighlightObject,
  173. selectObject,
  174. editSceneObjectByPath,
  175. }
  176. }