plugin.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import {
  2. setupDevtoolsPlugin,
  3. } from '@vue/devtools-api'
  4. import { Color, type Mesh } from 'three'
  5. import { reactive } from 'vue'
  6. import type {
  7. App as DevtoolsApp,
  8. } from '@vue/devtools-api'
  9. import { createHighlightMesh, editSceneObject } from '../utils'
  10. import * as is from '../utils/is'
  11. import { bytesToKB, calculateMemoryUsage } from '../utils/perf'
  12. import { toastMessage } from './utils'
  13. import type { TresContext } from '../composables'
  14. import type { TresObject } from './../types'
  15. export interface Tags {
  16. label: string
  17. textColor: number
  18. backgroundColor: number
  19. tooltip?: string
  20. }
  21. export interface SceneGraphObject {
  22. id: string
  23. label: string
  24. children: SceneGraphObject[]
  25. tags: Tags[]
  26. }
  27. const createNode = (object: TresObject): SceneGraphObject => {
  28. const node: SceneGraphObject = {
  29. id: object.uuid,
  30. label: object.type,
  31. children: [],
  32. tags: [],
  33. }
  34. if (object.name !== '') {
  35. node.tags.push({
  36. label: object.name,
  37. textColor: 0x57BF65,
  38. backgroundColor: 0xF0FCF3,
  39. })
  40. }
  41. const memory = calculateMemoryUsage(object)
  42. if (memory > 0) {
  43. node.tags.push({
  44. label: `${bytesToKB(memory)} KB`,
  45. textColor: 0xEFAC35,
  46. backgroundColor: 0xFFF9DC,
  47. tooltip: 'Memory usage',
  48. })
  49. }
  50. if (object.type.includes('Light')) {
  51. if (is.light(object)) {
  52. node.tags.push({
  53. label: `${object.intensity}`,
  54. textColor: 0x9499A6,
  55. backgroundColor: 0xF8F9FA,
  56. tooltip: 'Intensity',
  57. })
  58. }
  59. node.tags.push({
  60. label: `#${new Color(object.color).getHexString()}`,
  61. textColor: 0x9499A6,
  62. backgroundColor: 0xF8F9FA,
  63. tooltip: 'Color',
  64. })
  65. }
  66. if (object.type.includes('Camera')) {
  67. node.tags.push({
  68. label: `${object.fov}°`,
  69. textColor: 0x9499A6,
  70. backgroundColor: 0xF8F9FA,
  71. tooltip: 'Field of view',
  72. })
  73. node.tags.push({
  74. label: `x: ${Math.round(object.position.x)} y: ${Math.round(object.position.y)} z: ${Math.round(object.position.z)}`,
  75. textColor: 0x9499A6,
  76. backgroundColor: 0xF8F9FA,
  77. tooltip: 'Position',
  78. })
  79. }
  80. /* if (object.position) {
  81. node.tags.push({
  82. label: `x: ${object.position.x} y: ${object.position.y} z: ${object.position.z}`,
  83. textColor: 0x9499A6,
  84. backgroundColor: 0xF8F9FA,
  85. tooltip: 'Position',
  86. })
  87. } */
  88. return node
  89. }
  90. function buildGraph(object: TresObject, node: SceneGraphObject, filter: string = '') {
  91. object.children.forEach((child: TresObject) => {
  92. if (child.type === 'HightlightMesh') { return }
  93. if (filter && !child.type.includes(filter) && !child.name.includes(filter)) { return }
  94. const childNode = createNode(child)
  95. node.children.push(childNode)
  96. buildGraph(child, childNode, filter)
  97. })
  98. }
  99. const componentStateTypes: string[] = []
  100. const INSPECTOR_ID = 'tres:inspector'
  101. const state = reactive({
  102. sceneGraph: null as SceneGraphObject | null,
  103. })
  104. export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
  105. setupDevtoolsPlugin(
  106. {
  107. id: 'dev.esm.tres',
  108. label: 'TresJS 🪐',
  109. logo: 'https://raw.githubusercontent.com/Tresjs/tres/main/public/favicon.svg',
  110. packageName: 'tresjs',
  111. homepage: 'https://tresjs.org',
  112. componentStateTypes,
  113. app,
  114. },
  115. (api) => {
  116. if (typeof api.now !== 'function') {
  117. toastMessage(
  118. '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.',
  119. )
  120. }
  121. api.addInspector({
  122. id: INSPECTOR_ID,
  123. label: 'TresJS 🪐',
  124. icon: 'account_tree',
  125. treeFilterPlaceholder: 'Search instances',
  126. })
  127. setInterval(() => {
  128. api.sendInspectorTree(INSPECTOR_ID)
  129. }, 1000)
  130. setInterval(() => {
  131. api.notifyComponentUpdate()
  132. }, 5000)
  133. api.on.getInspectorTree((payload) => {
  134. if (payload.inspectorId === INSPECTOR_ID) {
  135. // Your logic here
  136. const root = createNode(tres.scene.value as unknown as TresObject)
  137. buildGraph(tres.scene.value as unknown as TresObject, root, payload.filter)
  138. state.sceneGraph = root
  139. payload.rootNodes = [root]
  140. }
  141. })
  142. let highlightMesh: Mesh | null = null
  143. let prevInstance: TresObject | null = null
  144. api.on.getInspectorState((payload) => {
  145. if (payload.inspectorId === INSPECTOR_ID) {
  146. // Your logic here
  147. const [instance] = tres.scene.value.getObjectsByProperty('uuid', payload.nodeId) as TresObject[]
  148. if (!instance) { return }
  149. if (prevInstance && highlightMesh && highlightMesh.parent) {
  150. prevInstance.remove(highlightMesh)
  151. }
  152. if (instance.isMesh) {
  153. const newHighlightMesh = createHighlightMesh(instance)
  154. instance.add(newHighlightMesh)
  155. highlightMesh = newHighlightMesh
  156. prevInstance = instance
  157. }
  158. payload.state = {
  159. object: Object.entries(instance)
  160. .map(([key, value]) => {
  161. if (key === 'children') {
  162. return { key, value: value.filter((child: { type: string }) => child.type !== 'HightlightMesh') }
  163. }
  164. return { key, value, editable: true }
  165. })
  166. .filter(({ key }) => {
  167. return key !== 'parent'
  168. }),
  169. }
  170. if (instance.isScene) {
  171. payload.state.info = {
  172. objects: instance.children.length,
  173. memory: calculateMemoryUsage(instance),
  174. calls: tres.renderer.value.info.render.calls,
  175. triangles: tres.renderer.value.info.render.triangles,
  176. points: tres.renderer.value.info.render.points,
  177. lines: tres.renderer.value.info.render.lines,
  178. }
  179. payload.state.programs = tres.renderer.value.info.programs?.map(program => ({
  180. key: program.name,
  181. value: {
  182. ...program,
  183. vertexShader: program.vertexShader,
  184. attributes: program.getAttributes(),
  185. uniforms: program.getUniforms(),
  186. },
  187. })) || []
  188. }
  189. }
  190. })
  191. api.on.editInspectorState((payload) => {
  192. if (payload.inspectorId === INSPECTOR_ID) {
  193. editSceneObject(tres.scene.value, payload.nodeId, payload.path, payload.state.value)
  194. }
  195. })
  196. },
  197. )
  198. }