plugin.ts 8.2 KB

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