@vgai/editor 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +35 -0
  3. package/dist/a/b/c/test-nested.json +1 -0
  4. package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
  5. package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
  6. package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
  7. package/dist/assets/browserAll-CEkWYlFk.js +1 -0
  8. package/dist/assets/collision-world-DniMB0qq.glb +0 -0
  9. package/dist/assets/create-runtime-DYIWr3va.js +1 -0
  10. package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
  11. package/dist/assets/esbuild-BHljloGq.wasm +0 -0
  12. package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
  13. package/dist/assets/game-8SDZPPgH.js +1 -0
  14. package/dist/assets/game-BI111o5f.js +10 -0
  15. package/dist/assets/game-CevorxyV.js +1 -0
  16. package/dist/assets/game-Di1H0gFj.js +1 -0
  17. package/dist/assets/game-Di7XlywD.js +1 -0
  18. package/dist/assets/game-DjnCaw8R.js +1 -0
  19. package/dist/assets/game-Q9A0aXSV.js +1 -0
  20. package/dist/assets/game-pt_1lQLv.js +1 -0
  21. package/dist/assets/game-source-CvidN7AB.js +1 -0
  22. package/dist/assets/game-xsOho_wg.js +83 -0
  23. package/dist/assets/index-Bp5cecD6.js +12 -0
  24. package/dist/assets/index-CPVV6pjw.js +1 -0
  25. package/dist/assets/index-CpOnYyeD.js +213 -0
  26. package/dist/assets/index-Cs8xwv3e.js +1 -0
  27. package/dist/assets/index-DL7RcQ0n.js +27 -0
  28. package/dist/assets/index-Dr8H_6Rl.js +1 -0
  29. package/dist/assets/index-_bCaipUk.js +13650 -0
  30. package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
  31. package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
  32. package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
  33. package/dist/assets/scene-query-BivAHhJu.js +1 -0
  34. package/dist/assets/stats.module--VATS4Kh.js +1 -0
  35. package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
  36. package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
  37. package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
  38. package/dist/index.html +37 -0
  39. package/dist/ingest/racing-game/UPSTREAM.md +68 -0
  40. package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
  41. package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
  42. package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
  43. package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
  44. package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
  45. package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
  46. package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
  47. package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
  48. package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
  49. package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
  50. package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
  51. package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
  52. package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
  53. package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
  54. package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
  55. package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
  56. package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
  57. package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
  58. package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
  59. package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
  60. package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
  61. package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
  62. package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
  63. package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
  64. package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
  65. package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
  66. package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
  67. package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
  68. package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
  69. package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
  70. package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
  71. package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
  72. package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
  73. package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
  74. package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
  75. package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
  76. package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
  77. package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
  78. package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
  79. package/dist/ingest/racing-game/a/index.css +1 -0
  80. package/dist/ingest/racing-game/images/bronze.png +0 -0
  81. package/dist/ingest/racing-game/images/gold.png +0 -0
  82. package/dist/ingest/racing-game/images/silver.png +0 -0
  83. package/dist/ingest/racing-game/index.html +15 -0
  84. package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
  85. package/dist/ingest/racing-game/models/track-draco.glb +0 -0
  86. package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
  87. package/dist/ingest/racing-game/rg-entry.js +1 -0
  88. package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
  89. package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
  90. package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
  91. package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
  92. package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
  93. package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
  94. package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
  95. package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
  96. package/dist/ingest/racing-game/textures/cursor.svg +3 -0
  97. package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
  98. package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
  99. package/dist/ingest/racing-game/textures/mask.svg +11 -0
  100. package/dist/ingest/racing-game/textures/no_sound.png +0 -0
  101. package/dist/ingest/racing-game/three-r139.module.js +50617 -0
  102. package/dist/ingest/racing-game/vendor.js +179 -0
  103. package/dist/ingest/racing-game/vgai.game.json +20 -0
  104. package/dist/ingest/simcity/UPSTREAM.md +65 -0
  105. package/dist/ingest/simcity/favicon.ico +0 -0
  106. package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
  107. package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
  108. package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
  109. package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
  110. package/dist/ingest/simcity/icons/bulldozer.png +0 -0
  111. package/dist/ingest/simcity/icons/calendar.png +0 -0
  112. package/dist/ingest/simcity/icons/factory-color.png +0 -0
  113. package/dist/ingest/simcity/icons/factory.png +0 -0
  114. package/dist/ingest/simcity/icons/house-color.png +0 -0
  115. package/dist/ingest/simcity/icons/house.png +0 -0
  116. package/dist/ingest/simcity/icons/job.png +0 -0
  117. package/dist/ingest/simcity/icons/office.png +0 -0
  118. package/dist/ingest/simcity/icons/pause-color.png +0 -0
  119. package/dist/ingest/simcity/icons/pause.png +0 -0
  120. package/dist/ingest/simcity/icons/person.png +0 -0
  121. package/dist/ingest/simcity/icons/play-color.png +0 -0
  122. package/dist/ingest/simcity/icons/play.png +0 -0
  123. package/dist/ingest/simcity/icons/power-color.png +0 -0
  124. package/dist/ingest/simcity/icons/power-line-color.png +0 -0
  125. package/dist/ingest/simcity/icons/power.png +0 -0
  126. package/dist/ingest/simcity/icons/road-color.png +0 -0
  127. package/dist/ingest/simcity/icons/road.png +0 -0
  128. package/dist/ingest/simcity/icons/select-color.png +0 -0
  129. package/dist/ingest/simcity/icons/select.png +0 -0
  130. package/dist/ingest/simcity/icons/store-color.png +0 -0
  131. package/dist/ingest/simcity/index.html +94 -0
  132. package/dist/ingest/simcity/index.js +58 -0
  133. package/dist/ingest/simcity/main.css +233 -0
  134. package/dist/ingest/simcity/models/armored-truck.glb +0 -0
  135. package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
  136. package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
  137. package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
  138. package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
  139. package/dist/ingest/simcity/models/building-cafe.glb +0 -0
  140. package/dist/ingest/simcity/models/building-casino.glb +0 -0
  141. package/dist/ingest/simcity/models/building-cinema.glb +0 -0
  142. package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
  143. package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
  144. package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
  145. package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
  146. package/dist/ingest/simcity/models/building-office-big.glb +0 -0
  147. package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
  148. package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
  149. package/dist/ingest/simcity/models/building-office.glb +0 -0
  150. package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
  151. package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
  152. package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
  153. package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
  154. package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
  155. package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
  156. package/dist/ingest/simcity/models/car-passenger.glb +0 -0
  157. package/dist/ingest/simcity/models/car-police.glb +0 -0
  158. package/dist/ingest/simcity/models/car-taxi.glb +0 -0
  159. package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
  160. package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
  161. package/dist/ingest/simcity/models/car-veteran.glb +0 -0
  162. package/dist/ingest/simcity/models/construction-small.glb +0 -0
  163. package/dist/ingest/simcity/models/data-center.glb +0 -0
  164. package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
  165. package/dist/ingest/simcity/models/industry-factory.glb +0 -0
  166. package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
  167. package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
  168. package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
  169. package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
  170. package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
  171. package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
  172. package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
  173. package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
  174. package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
  175. package/dist/ingest/simcity/models/truck.glb +0 -0
  176. package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
  177. package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
  178. package/dist/ingest/simcity/textures/base.png +0 -0
  179. package/dist/ingest/simcity/textures/grid.png +0 -0
  180. package/dist/ingest/simcity/textures/specular.png +0 -0
  181. package/dist/ingest/simcity/three-r155.module.js +51466 -0
  182. package/dist/ingest/simcity/vgai.game.json +20 -0
  183. package/dist/ingest/tanks/UPSTREAM.md +69 -0
  184. package/dist/ingest/tanks/index.html +214 -0
  185. package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
  186. package/dist/ingest/tanks/tanks-entry.js +92 -0
  187. package/dist/ingest/tanks/three-r170.module.js +54571 -0
  188. package/dist/ingest/tanks/vgai.game.json +20 -0
  189. package/dist/inputmaps/default.inputmap.json +41 -0
  190. package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  191. package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  192. package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  193. package/dist/scenes/untitled.vscn.json +29 -0
  194. package/dist/textures/waternormals.jpg +0 -0
  195. package/dist-server/packaged.mjs +18155 -0
  196. package/package.json +36 -0
  197. package/server/asset-library-routes.ts +833 -0
  198. package/server/canonical-path.ts +50 -0
  199. package/server/cloud-asset-catalog.ts +101 -0
  200. package/server/dev.ts +350 -0
  201. package/server/editor-server.ts +1537 -0
  202. package/server/editor-sse.ts +73 -0
  203. package/server/game-globals-shadow.ts +58 -0
  204. package/server/local-asset-catalog.ts +163 -0
  205. package/server/packaged.ts +571 -0
  206. package/server/prod.ts +75 -0
  207. package/server/project-optimize-deps-entries.ts +149 -0
  208. package/server/project-view.ts +65 -0
  209. package/server/server-utils.ts +430 -0
  210. package/server/session-registry.ts +93 -0
  211. package/src/EditorContext.tsx +87 -0
  212. package/src/achieved-tier.ts +126 -0
  213. package/src/action-registry.ts +268 -0
  214. package/src/adapter-resolver.ts +1858 -0
  215. package/src/asset-events.ts +100 -0
  216. package/src/authoring/active-adapter.ts +98 -0
  217. package/src/authoring/active-systems.ts +50 -0
  218. package/src/authoring/boundary-authoring-adapter.ts +178 -0
  219. package/src/authoring/composite-authoring-adapter.ts +891 -0
  220. package/src/authoring/design-time-layers.ts +776 -0
  221. package/src/authoring/edit-mode-authoring.ts +643 -0
  222. package/src/authoring/embed-only-adapter.ts +57 -0
  223. package/src/authoring/ephemeral-persistence.ts +26 -0
  224. package/src/authoring/eyedropper-session.ts +69 -0
  225. package/src/authoring/first-party-hierarchy-menu.ts +74 -0
  226. package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
  227. package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
  228. package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
  229. package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
  230. package/src/authoring/first-party-inspector/register.tsx +108 -0
  231. package/src/authoring/ingest-authoring-adapter.ts +475 -0
  232. package/src/authoring/ingest-capture.ts +94 -0
  233. package/src/authoring/ingest-game-adapter.ts +291 -0
  234. package/src/authoring/ingest-iframe-adapter.ts +38 -0
  235. package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
  236. package/src/authoring/ingest-overlay.ts +193 -0
  237. package/src/authoring/layered-pick.ts +87 -0
  238. package/src/authoring/mount-failure-report.ts +112 -0
  239. package/src/authoring/overlay-report.ts +52 -0
  240. package/src/authoring/project-scenes.ts +111 -0
  241. package/src/authoring/react-dom-authoring-adapter.ts +795 -0
  242. package/src/authoring/react-inspector-section.tsx +1041 -0
  243. package/src/authoring/react-world-authoring-adapter.ts +2400 -0
  244. package/src/authoring/root-activation.ts +61 -0
  245. package/src/authoring/scene-ui-layer.ts +485 -0
  246. package/src/authoring/story-overlay.ts +48 -0
  247. package/src/authoring/ui-authoring-adapter.ts +325 -0
  248. package/src/authoring/ui-hierarchy-menu.ts +40 -0
  249. package/src/authoring/ui-inspector-sections.tsx +156 -0
  250. package/src/authoring/undo-timeline.ts +145 -0
  251. package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
  252. package/src/authoring/viewport-pick-context.ts +31 -0
  253. package/src/authoring/viewport-raycast.ts +80 -0
  254. package/src/authoring/world-overlay.ts +76 -0
  255. package/src/authoring/world-pan-state.ts +115 -0
  256. package/src/authoring/world-session-state.ts +102 -0
  257. package/src/authoring/world2d-authoring-adapter.ts +313 -0
  258. package/src/browser-external-watch.ts +98 -0
  259. package/src/browser-play-entry.ts +112 -0
  260. package/src/browser-project-scripts.ts +112 -0
  261. package/src/browser-transpile.ts +246 -0
  262. package/src/browser-watch-reload.ts +100 -0
  263. package/src/command-listener.ts +326 -0
  264. package/src/components/AlignToolbar.tsx +189 -0
  265. package/src/components/AppRoot.tsx +73 -0
  266. package/src/components/AssetBrowser.tsx +502 -0
  267. package/src/components/BottomPanel.tsx +326 -0
  268. package/src/components/BuildPanel.tsx +455 -0
  269. package/src/components/CameraInfo.tsx +56 -0
  270. package/src/components/CommandPalette.tsx +265 -0
  271. package/src/components/ConsolePanel.tsx +272 -0
  272. package/src/components/DataPanel.tsx +1268 -0
  273. package/src/components/DefaultEditorLayout.tsx +509 -0
  274. package/src/components/FolderTree.tsx +179 -0
  275. package/src/components/GameHierarchy.tsx +1349 -0
  276. package/src/components/Inspector.tsx +671 -0
  277. package/src/components/InspectorToolSection.tsx +74 -0
  278. package/src/components/MountFailureBanner.tsx +110 -0
  279. package/src/components/NetworkSection.tsx +109 -0
  280. package/src/components/OnlineAssetBrowser.tsx +448 -0
  281. package/src/components/OverlayReportBanner.tsx +122 -0
  282. package/src/components/PlayBar.tsx +356 -0
  283. package/src/components/PrefabSaveDialog.tsx +201 -0
  284. package/src/components/ProjectHeader.tsx +174 -0
  285. package/src/components/ProjectScreen.tsx +1004 -0
  286. package/src/components/RightPanel.tsx +166 -0
  287. package/src/components/RootActivateDialog.tsx +105 -0
  288. package/src/components/SaveDialog.tsx +197 -0
  289. package/src/components/SaveStatus.tsx +69 -0
  290. package/src/components/StatsOverlay.tsx +35 -0
  291. package/src/components/ToolHost.tsx +80 -0
  292. package/src/components/Toolbar.tsx +307 -0
  293. package/src/components/ViewportOverlay.tsx +234 -0
  294. package/src/components/ViewportPanel.tsx +369 -0
  295. package/src/components/WorldContextMenu.tsx +257 -0
  296. package/src/components/WorldSelectionOverlay.tsx +2003 -0
  297. package/src/components/WorldTextEditor.tsx +86 -0
  298. package/src/components/asset-viewers/AssetViewer.tsx +38 -0
  299. package/src/components/asset-viewers/AudioViewer.tsx +149 -0
  300. package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
  301. package/src/components/asset-viewers/ImageViewer.tsx +65 -0
  302. package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
  303. package/src/components/asset-viewers/ModelViewer.tsx +180 -0
  304. package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
  305. package/src/components/data-csv.ts +301 -0
  306. package/src/components/entity-name-commit.ts +18 -0
  307. package/src/components/inspector-property-grouping.ts +65 -0
  308. package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
  309. package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
  310. package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
  311. package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
  312. package/src/components/inspector-widgets/FontPicker.tsx +196 -0
  313. package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
  314. package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
  315. package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
  316. package/src/components/inspector-widgets/color-utils.ts +202 -0
  317. package/src/components/inspector-widgets/gallery-main.tsx +167 -0
  318. package/src/components/inspector-widgets/index.ts +22 -0
  319. package/src/components/inspector-widgets/shared.tsx +135 -0
  320. package/src/components/inspectors/AnimationSection.tsx +143 -0
  321. package/src/components/inspectors/AudioSection.tsx +215 -0
  322. package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
  323. package/src/components/inspectors/CameraSection.tsx +105 -0
  324. package/src/components/inspectors/ComponentsSection.tsx +358 -0
  325. package/src/components/inspectors/JointsSection.tsx +255 -0
  326. package/src/components/inspectors/LightSection.tsx +116 -0
  327. package/src/components/inspectors/MaterialSection.tsx +1189 -0
  328. package/src/components/inspectors/MeshSection.tsx +99 -0
  329. package/src/components/inspectors/NavigationSection.tsx +58 -0
  330. package/src/components/inspectors/ParticleSection.tsx +681 -0
  331. package/src/components/inspectors/PhysicsSection.tsx +326 -0
  332. package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
  333. package/src/components/inspectors/RenderingSection.tsx +109 -0
  334. package/src/components/inspectors/ShadowSection.tsx +84 -0
  335. package/src/components/inspectors/SplineSection.tsx +191 -0
  336. package/src/components/inspectors/TransformSection.tsx +191 -0
  337. package/src/components/inspectors/json-schema-fields.ts +281 -0
  338. package/src/components/inspectors/multi-edit-utils.ts +49 -0
  339. package/src/components/inspectors/zod-fields.ts +184 -0
  340. package/src/components/primitives/ColorInput.tsx +109 -0
  341. package/src/components/primitives/JsonInput.tsx +98 -0
  342. package/src/components/primitives/NumberInput.tsx +164 -0
  343. package/src/components/primitives/Panel.tsx +68 -0
  344. package/src/components/primitives/SectionHeader.tsx +45 -0
  345. package/src/components/primitives/Tooltip.tsx +82 -0
  346. package/src/components/primitives/Vec3Input.tsx +62 -0
  347. package/src/components/world-overlay-gestures.ts +1438 -0
  348. package/src/doctor/data-checks.ts +285 -0
  349. package/src/doctor/report.ts +386 -0
  350. package/src/editor-api.ts +700 -0
  351. package/src/editor-console.ts +194 -0
  352. package/src/editor-hotkeys.ts +441 -0
  353. package/src/editor-layers.ts +2 -0
  354. package/src/editor-mode.ts +28 -0
  355. package/src/editor-store.ts +2225 -0
  356. package/src/editor-viewport.ts +1924 -0
  357. package/src/entity-factory.ts +221 -0
  358. package/src/game-globals-prelude.ts +27 -0
  359. package/src/gated-globals.ts +131 -0
  360. package/src/generated/setup-by-example.generated.ts +53 -0
  361. package/src/gizmo-registry.ts +608 -0
  362. package/src/handle-system.ts +221 -0
  363. package/src/hierarchy-menu-registry.ts +70 -0
  364. package/src/hierarchy-rows.ts +73 -0
  365. package/src/hosted-example.ts +160 -0
  366. package/src/hotkeys.ts +142 -0
  367. package/src/ingest/discovery-react.ts +132 -0
  368. package/src/ingest/discovery.ts +205 -0
  369. package/src/ingest/discovery2d.ts +204 -0
  370. package/src/ingest/esm-entry-resolution.ts +102 -0
  371. package/src/ingest/games/bloom-composer/game.js +99 -0
  372. package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
  373. package/src/ingest/games/embed-bundle/embed.html +10 -0
  374. package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
  375. package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
  376. package/src/ingest/games/games-fps/game.js +479 -0
  377. package/src/ingest/games/games-fps/vgai.game.json +24 -0
  378. package/src/ingest/games/iframe-reachable/game-source.js +58 -0
  379. package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
  380. package/src/ingest/games/interactive-cubes/game.js +139 -0
  381. package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
  382. package/src/ingest/games/mismatched-three/fake-three.js +38 -0
  383. package/src/ingest/games/mismatched-three/game.js +10 -0
  384. package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
  385. package/src/ingest/games/own-three-bundled/game.js +43 -0
  386. package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
  387. package/src/ingest/games/pointerlock-maze/game.js +288 -0
  388. package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
  389. package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
  390. package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
  391. package/src/ingest/games-2d/bunnymark/game.ts +88 -0
  392. package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
  393. package/src/ingest/games-2d/flappy/game.ts +148 -0
  394. package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
  395. package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
  396. package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
  397. package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
  398. package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
  399. package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
  400. package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
  401. package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
  402. package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
  403. package/src/ingest/host-namespace-registry.ts +196 -0
  404. package/src/ingest/types.ts +127 -0
  405. package/src/ingest-hierarchy-rows.ts +207 -0
  406. package/src/ingest-mode.ts +2092 -0
  407. package/src/ingest-siblings.ts +466 -0
  408. package/src/initial-scene.ts +194 -0
  409. package/src/inspector-section-registry.ts +117 -0
  410. package/src/interactive-edit-scope.ts +34 -0
  411. package/src/main.tsx +23 -0
  412. package/src/manifest-project.ts +43 -0
  413. package/src/model-thumbnail.ts +156 -0
  414. package/src/module-mode.ts +513 -0
  415. package/src/navmesh-handler.ts +94 -0
  416. package/src/packaged-runtime.ts +49 -0
  417. package/src/play-mode.ts +1148 -0
  418. package/src/playground/main.ts +206 -0
  419. package/src/project-manager.ts +173 -0
  420. package/src/scene-diff.ts +112 -0
  421. package/src/scene-serializer.ts +144 -0
  422. package/src/scene-sync.ts +587 -0
  423. package/src/scene-ui-mode/active-scene-ui.ts +58 -0
  424. package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
  425. package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
  426. package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
  427. package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
  428. package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
  429. package/src/script-loader.ts +143 -0
  430. package/src/storage/fsa-storage.ts +55 -0
  431. package/src/storage/handle-storage.ts +150 -0
  432. package/src/storage/handle-store.ts +116 -0
  433. package/src/storage/http-storage.ts +137 -0
  434. package/src/storage/index.ts +147 -0
  435. package/src/storage/mem-storage.ts +135 -0
  436. package/src/storage/opfs-storage.ts +25 -0
  437. package/src/storage/paths.ts +26 -0
  438. package/src/storage/seed.ts +106 -0
  439. package/src/storage/types.ts +110 -0
  440. package/src/tool-loader.ts +291 -0
  441. package/src/ui-editor/inspector.tsx +563 -0
  442. package/src/ui-editor/node-ops.ts +266 -0
  443. package/src/ui-editor/overlay.tsx +380 -0
  444. package/src/ui-editor/palette.tsx +114 -0
  445. package/src/ui-editor/react-store.ts +78 -0
  446. package/src/ui-editor/timeline-panel.tsx +443 -0
  447. package/src/ui-editor/ui-edit-store.ts +552 -0
  448. package/src/ui-editor/ui-prefab.ts +95 -0
  449. package/src/ui-source/inspect.ts +544 -0
  450. package/src/ui-source/oid-transform.ts +133 -0
  451. package/src/ui-source/source-write-backend.ts +203 -0
  452. package/src/ui-source/tailwind-map.ts +174 -0
  453. package/src/ui-source/writer.ts +1175 -0
  454. package/src/widgets/index.ts +73 -0
  455. package/template/.claude/skills/editor/SKILL.md +252 -0
  456. package/template/CLAUDE.md +512 -0
  457. package/template/LICENSE +30 -0
  458. package/template/emit-schemas.ts +42 -0
  459. package/template/index.html +17 -0
  460. package/template/package.json +53 -0
  461. package/template/project.json +9 -0
  462. package/template/public/audio/ambient-arena.wav +0 -0
  463. package/template/public/audio/ambient-music.wav +0 -0
  464. package/template/public/audio/ambient-nature.wav +0 -0
  465. package/template/public/audio/ambient-wind.wav +0 -0
  466. package/template/public/inputmaps/default.inputmap.json +41 -0
  467. package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
  468. package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
  469. package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
  470. package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
  471. package/template/public/scenes/default.vscn.json +43 -0
  472. package/template/public/scenes/untitled.vscn.json +29 -0
  473. package/template/public/textures/waternormals.jpg +0 -0
  474. package/template/server/colyseus-setup.ts +85 -0
  475. package/template/server/main.ts +34 -0
  476. package/template/server/rooms/arena-room.ts +420 -0
  477. package/template/server/rooms/game-room.ts +162 -0
  478. package/template/server/rooms.ts +15 -0
  479. package/template/server/tsconfig.json +19 -0
  480. package/template/src/data/README.md +40 -0
  481. package/template/src/data/assets.ts +31 -0
  482. package/template/src/data/tuning.data.json +5 -0
  483. package/template/src/data/tuning.schema.json +21 -0
  484. package/template/src/data/tuning.schema.ts +34 -0
  485. package/template/src/data/tuning.ts +24 -0
  486. package/template/src/main.ts +157 -0
  487. package/template/src/scripts/components/data-spinner.ts +45 -0
  488. package/template/src/scripts/components/scene-camera.ts +40 -0
  489. package/template/src/scripts/main.ts +94 -0
  490. package/template/src/scripts/registry.ts +46 -0
  491. package/template/src/tools/README.md +14 -0
  492. package/template/src/tools/example.tool.tsx +91 -0
  493. package/template/src/tools/spin.tool.tsx +101 -0
  494. package/template/src/ui/Button.tsx +27 -0
  495. package/template/src/ui/DefaultHud.tsx +40 -0
  496. package/template/src/ui/KeyHint.tsx +25 -0
  497. package/template/src/ui/Panel.tsx +52 -0
  498. package/template/src/ui/RebindPanel.tsx +129 -0
  499. package/template/src/ui/Slider.tsx +28 -0
  500. package/template/src/ui/StatusDot.tsx +22 -0
  501. package/template/src/ui/Toggle.tsx +19 -0
  502. package/template/src/ui/game-state.tsx +37 -0
  503. package/template/src/ui/index.ts +7 -0
  504. package/template/src/ui/mount-default-hud.tsx +19 -0
  505. package/template/src/ui/mount-ui.ts +15 -0
  506. package/template/tsconfig.json +36 -0
  507. package/template/validate-asset-content.ts +78 -0
  508. package/template/validate-manifest.ts +55 -0
  509. package/template/validate-scenes.ts +211 -0
  510. package/template/vgai.game.json +21 -0
  511. package/template/vite.config.ts +128 -0
  512. package/vite-plugin-example-assets.ts +60 -0
  513. package/vite-plugin-game-globals.ts +46 -0
  514. package/vite-plugin-game-static.ts +71 -0
  515. package/vite-plugin-project-game-static.ts +84 -0
  516. package/vite-plugin-react-game-static.ts +98 -0
  517. package/vite-plugin-react-world-provider.ts +81 -0
  518. package/vite-plugin-react-world-runtime.ts +76 -0
  519. package/vite-plugin-ui-oid.ts +561 -0
@@ -0,0 +1,608 @@
1
+ /**
2
+ * Gizmo registry — maps entity types to their editor visual representation.
3
+ *
4
+ * Inspired by Godot's EditorNode3DGizmoPlugin pattern: each entity type
5
+ * registers a gizmo definition that describes:
6
+ * - icon: a clickable billboard sprite (for selecting non-mesh entities)
7
+ * - helpers: wireframe visualizations (frustums, ranges, collider outlines)
8
+ *
9
+ * Icons are raycastable (for click-to-select). Helpers are visual-only.
10
+ * Both live on EDITOR_LAYER so the game camera never sees them.
11
+ */
12
+
13
+ import {
14
+ type ColliderWorldDimensions,
15
+ computeColliderWorldDimensions,
16
+ } from '@engine/scene/collider-dimensions';
17
+ import { DEFAULTS } from '@engine/scene/defaults';
18
+ import type {
19
+ SceneCollider,
20
+ SceneEntity,
21
+ SceneLight,
22
+ SceneSpline,
23
+ } from '@engine/scene/scene-types';
24
+ import { getUserData, setUserData } from '@engine/scene/user-data';
25
+ import * as THREE from 'three';
26
+ import { EDITOR_LAYER } from './editor-layers';
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Registry types
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /** A gizmo definition for a particular entity feature. */
33
+ interface GizmoEntry {
34
+ /**
35
+ * Test whether this gizmo applies to the given entity.
36
+ * Returning false skips both icon and helpers.
37
+ */
38
+ test(entity: SceneEntity, obj: THREE.Object3D): boolean;
39
+
40
+ /**
41
+ * Create a clickable icon billboard.
42
+ * The sprite is NOT tagged editorHelper — it participates in raycast selection.
43
+ */
44
+ createIcon(): THREE.Sprite;
45
+
46
+ /**
47
+ * Create wireframe helper objects (frustums, ranges, outlines, etc.).
48
+ * Each returned object is tagged editorHelper and excluded from raycasts.
49
+ */
50
+ createHelpers(entity: SceneEntity, obj: THREE.Object3D): THREE.Object3D[];
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Icon billboard factory (shared by all entries)
55
+ // ---------------------------------------------------------------------------
56
+
57
+ const iconTextureCache = new Map<string, THREE.Texture>();
58
+
59
+ function makeIconSprite(symbol: string, cacheKey: string): THREE.Sprite {
60
+ let texture = iconTextureCache.get(cacheKey);
61
+ if (!texture) {
62
+ const size = 64;
63
+ const c = document.createElement('canvas');
64
+ c.width = size;
65
+ c.height = size;
66
+ const ctx = c.getContext('2d')!;
67
+ ctx.font = '48px system-ui, -apple-system, sans-serif';
68
+ ctx.textAlign = 'center';
69
+ ctx.textBaseline = 'middle';
70
+ ctx.fillText(symbol, size / 2, size / 2);
71
+ texture = new THREE.CanvasTexture(c);
72
+ iconTextureCache.set(cacheKey, texture);
73
+ }
74
+ const mat = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false });
75
+ const sprite = new THREE.Sprite(mat);
76
+ sprite.scale.set(0.5, 0.5, 0.5);
77
+ setUserData(sprite, 'editorIcon', true);
78
+ sprite.layers.set(EDITOR_LAYER);
79
+ return sprite;
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Shared geometry helpers
84
+ // ---------------------------------------------------------------------------
85
+
86
+ function createCircleGeometry(radius: number, segments = 32): THREE.BufferGeometry {
87
+ const pts: THREE.Vector3[] = [];
88
+ for (let i = 0; i <= segments; i++) {
89
+ const angle = (i / segments) * Math.PI * 2;
90
+ pts.push(new THREE.Vector3(Math.cos(angle) * radius, Math.sin(angle) * radius, 0));
91
+ }
92
+ return new THREE.BufferGeometry().setFromPoints(pts);
93
+ }
94
+
95
+ function createSemiArcGeometry(
96
+ radius: number,
97
+ centerY: number,
98
+ plane: 'xz' | 'yz',
99
+ top: boolean,
100
+ segments = 16,
101
+ ): THREE.BufferGeometry {
102
+ const pts: THREE.Vector3[] = [];
103
+ const startAngle = top ? 0 : Math.PI;
104
+ for (let i = 0; i <= segments; i++) {
105
+ const angle = startAngle + (i / segments) * Math.PI;
106
+ const h = Math.cos(angle) * radius;
107
+ const v = centerY + Math.sin(angle) * radius;
108
+ if (plane === 'xz') {
109
+ pts.push(new THREE.Vector3(h, v, 0));
110
+ } else {
111
+ pts.push(new THREE.Vector3(0, v, h));
112
+ }
113
+ }
114
+ return new THREE.BufferGeometry().setFromPoints(pts);
115
+ }
116
+
117
+ // ---------------------------------------------------------------------------
118
+ // Light gizmo
119
+ // ---------------------------------------------------------------------------
120
+
121
+ const COLLIDER_COLOR = 0x00ff88;
122
+ const SENSOR_COLOR = 0xffcc00;
123
+
124
+ function createLightHelper(light: THREE.Light, def: SceneLight): THREE.Object3D | null {
125
+ switch (def.type) {
126
+ case 'directional':
127
+ return new THREE.DirectionalLightHelper(light as THREE.DirectionalLight, 1);
128
+ case 'point':
129
+ return new THREE.PointLightHelper(light as THREE.PointLight, 0.5);
130
+ case 'spot':
131
+ return new THREE.SpotLightHelper(light as THREE.SpotLight);
132
+ default:
133
+ return null;
134
+ }
135
+ }
136
+
137
+ function createLightRangeHelper(distance: number, color: string): THREE.Object3D {
138
+ const mat = new THREE.LineBasicMaterial({
139
+ color: color,
140
+ transparent: true,
141
+ opacity: 0.3,
142
+ depthTest: false,
143
+ });
144
+ const group = new THREE.Group();
145
+ group.add(new THREE.Line(createCircleGeometry(distance, 48), mat));
146
+ const xzRing = new THREE.Line(createCircleGeometry(distance, 48), mat);
147
+ xzRing.rotation.x = -Math.PI / 2;
148
+ group.add(xzRing);
149
+ const yzRing = new THREE.Line(createCircleGeometry(distance, 48), mat);
150
+ yzRing.rotation.y = Math.PI / 2;
151
+ group.add(yzRing);
152
+ return group;
153
+ }
154
+
155
+ const lightGizmo: GizmoEntry = {
156
+ test: (entity) => !!entity.light,
157
+
158
+ createIcon: () => makeIconSprite('☀', 'light'),
159
+
160
+ createHelpers(entity, obj) {
161
+ const helpers: THREE.Object3D[] = [];
162
+ const lightDef = entity.light!;
163
+
164
+ if (obj instanceof THREE.Light) {
165
+ const helper = createLightHelper(obj, lightDef);
166
+ if (helper) helpers.push(helper);
167
+ }
168
+
169
+ if ((lightDef.type === 'point' || lightDef.type === 'spot') && lightDef.distance) {
170
+ helpers.push(
171
+ createLightRangeHelper(lightDef.distance, lightDef.color ?? DEFAULTS.light.color),
172
+ );
173
+ }
174
+
175
+ return helpers;
176
+ },
177
+ };
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Camera gizmo
181
+ // ---------------------------------------------------------------------------
182
+
183
+ function createFrustumHelper(camera: THREE.Camera): THREE.LineSegments {
184
+ const inv = camera.projectionMatrixInverse;
185
+ const ndc = [
186
+ [-1, -1, -1],
187
+ [1, -1, -1],
188
+ [1, 1, -1],
189
+ [-1, 1, -1],
190
+ [-1, -1, 1],
191
+ [1, -1, 1],
192
+ [1, 1, 1],
193
+ [-1, 1, 1],
194
+ ];
195
+ const c = ndc.map(([x, y, z]) => new THREE.Vector3(x, y, z).applyMatrix4(inv));
196
+ const [n0, n1, n2, n3, f0, f1, f2, f3] = c as [
197
+ THREE.Vector3,
198
+ THREE.Vector3,
199
+ THREE.Vector3,
200
+ THREE.Vector3,
201
+ THREE.Vector3,
202
+ THREE.Vector3,
203
+ THREE.Vector3,
204
+ THREE.Vector3,
205
+ ];
206
+ const verts: number[] = [];
207
+ function line(a: THREE.Vector3, b: THREE.Vector3) {
208
+ verts.push(a.x, a.y, a.z, b.x, b.y, b.z);
209
+ }
210
+ line(n0, n1);
211
+ line(n1, n2);
212
+ line(n2, n3);
213
+ line(n3, n0);
214
+ line(f0, f1);
215
+ line(f1, f2);
216
+ line(f2, f3);
217
+ line(f3, f0);
218
+ line(n0, f0);
219
+ line(n1, f1);
220
+ line(n2, f2);
221
+ line(n3, f3);
222
+
223
+ const geo = new THREE.BufferGeometry();
224
+ geo.setAttribute('position', new THREE.Float32BufferAttribute(verts, 3));
225
+ return new THREE.LineSegments(
226
+ geo,
227
+ new THREE.LineBasicMaterial({ color: 0xffaa00, depthTest: false }),
228
+ );
229
+ }
230
+
231
+ const cameraGizmo: GizmoEntry = {
232
+ test: (entity) => !!entity.camera,
233
+
234
+ createIcon: () => makeIconSprite('📷', 'camera'),
235
+
236
+ createHelpers(entity, obj) {
237
+ // Find existing Three.js camera (editor entities wrap in a Group with a Camera child)
238
+ let camera: THREE.Camera | null = null;
239
+ if (obj instanceof THREE.Camera) {
240
+ camera = obj;
241
+ } else {
242
+ obj.traverse((child) => {
243
+ if (!camera && child instanceof THREE.Camera) camera = child;
244
+ });
245
+ }
246
+
247
+ // Game scene objects don't have a Three.js Camera — build a temporary one from the entity def
248
+ if (!camera && entity.camera) {
249
+ const def = entity.camera;
250
+ const dCam = DEFAULTS.camera;
251
+ const aspect = (def.width ?? dCam.width) / (def.height ?? dCam.height);
252
+ if (def.type === 'orthographic') {
253
+ const size = def.orthoSize ?? dCam.orthoSize;
254
+ const ortho = new THREE.OrthographicCamera(
255
+ -size * aspect,
256
+ size * aspect,
257
+ size,
258
+ -size,
259
+ def.near ?? dCam.near,
260
+ def.far ?? dCam.far,
261
+ );
262
+ ortho.updateProjectionMatrix();
263
+ camera = ortho;
264
+ } else {
265
+ const persp = new THREE.PerspectiveCamera(
266
+ def.fov ?? dCam.fov,
267
+ aspect,
268
+ def.near ?? dCam.near,
269
+ def.far ?? dCam.far,
270
+ );
271
+ persp.updateProjectionMatrix();
272
+ camera = persp;
273
+ }
274
+ }
275
+
276
+ return camera ? [createFrustumHelper(camera)] : [];
277
+ },
278
+ };
279
+
280
+ // ---------------------------------------------------------------------------
281
+ // Audio gizmo
282
+ // ---------------------------------------------------------------------------
283
+
284
+ function createAudioHelper(
285
+ spatial: boolean,
286
+ refDistance: number,
287
+ maxDistance: number,
288
+ ): THREE.Object3D {
289
+ const group = new THREE.Group();
290
+ const mat = new THREE.LineBasicMaterial({
291
+ color: 0x44bbff,
292
+ transparent: true,
293
+ opacity: 0.6,
294
+ depthTest: false,
295
+ });
296
+
297
+ // Small speaker cone indicator
298
+ const speakerVerts: number[] = [];
299
+ speakerVerts.push(-0.1, -0.15, 0, -0.1, 0.15, 0);
300
+ speakerVerts.push(-0.1, 0.15, 0, -0.1, -0.15, 0);
301
+ speakerVerts.push(-0.1, 0.15, 0, 0.2, 0.3, 0);
302
+ speakerVerts.push(-0.1, -0.15, 0, 0.2, -0.3, 0);
303
+ speakerVerts.push(0.2, 0.3, 0, 0.2, -0.3, 0);
304
+ const speakerGeo = new THREE.BufferGeometry();
305
+ speakerGeo.setAttribute('position', new THREE.Float32BufferAttribute(speakerVerts, 3));
306
+ group.add(new THREE.LineSegments(speakerGeo, mat));
307
+
308
+ if (spatial) {
309
+ const refMat = new THREE.LineBasicMaterial({
310
+ color: 0x44bbff,
311
+ transparent: true,
312
+ opacity: 0.4,
313
+ depthTest: false,
314
+ });
315
+ const refRing = new THREE.Line(createCircleGeometry(refDistance, 32), refMat);
316
+ refRing.rotation.x = -Math.PI / 2;
317
+ group.add(refRing);
318
+
319
+ if (maxDistance < 10000) {
320
+ const maxMat = new THREE.LineBasicMaterial({
321
+ color: 0x44bbff,
322
+ transparent: true,
323
+ opacity: 0.15,
324
+ depthTest: false,
325
+ });
326
+ const maxRing = new THREE.Line(createCircleGeometry(maxDistance, 48), maxMat);
327
+ maxRing.rotation.x = -Math.PI / 2;
328
+ group.add(maxRing);
329
+ }
330
+ }
331
+
332
+ return group;
333
+ }
334
+
335
+ const audioGizmo: GizmoEntry = {
336
+ test: (entity) => !!entity.audio,
337
+
338
+ createIcon: () => makeIconSprite('🔊', 'audio'),
339
+
340
+ createHelpers(entity) {
341
+ const audioDef = entity.audio!;
342
+ const spatial = audioDef.spatial !== false;
343
+ return [
344
+ createAudioHelper(
345
+ spatial,
346
+ audioDef.refDistance ?? DEFAULTS.audio.refDistance,
347
+ audioDef.maxDistance ?? DEFAULTS.audio.maxDistance,
348
+ ),
349
+ ];
350
+ },
351
+ };
352
+
353
+ // ---------------------------------------------------------------------------
354
+ // Collider gizmo (no icon — colliders are on mesh entities)
355
+ // ---------------------------------------------------------------------------
356
+
357
+ /**
358
+ * Build the collider wireframe from WORLD-SCALED dimensions (T1.2) — the same
359
+ * numbers the runtime physics loader bakes into the actual Rapier collider
360
+ * (see `computeColliderWorldDimensions` in `@engine/scene/collider-dimensions`).
361
+ * `dims` already has the entity's composed world scale (self × ancestors)
362
+ * applied, so this function itself must NOT apply any further scale.
363
+ */
364
+ function createColliderHelper(
365
+ collider: SceneCollider,
366
+ dims: ColliderWorldDimensions,
367
+ ): THREE.Object3D | null {
368
+ const mat = new THREE.LineBasicMaterial({
369
+ color: collider.isSensor ? SENSOR_COLOR : COLLIDER_COLOR,
370
+ transparent: true,
371
+ opacity: 0.7,
372
+ depthTest: false,
373
+ });
374
+ const group = new THREE.Group();
375
+
376
+ switch (collider.type) {
377
+ case 'cuboid': {
378
+ const he = dims.halfExtents!;
379
+ const geo = new THREE.BoxGeometry(he[0] * 2, he[1] * 2, he[2] * 2);
380
+ group.add(new THREE.LineSegments(new THREE.EdgesGeometry(geo), mat));
381
+ break;
382
+ }
383
+ case 'ball': {
384
+ const r = dims.radius!;
385
+ group.add(new THREE.Line(createCircleGeometry(r), mat));
386
+ const xzRing = new THREE.Line(createCircleGeometry(r), mat);
387
+ xzRing.rotation.x = -Math.PI / 2;
388
+ group.add(xzRing);
389
+ const yzRing = new THREE.Line(createCircleGeometry(r), mat);
390
+ yzRing.rotation.y = Math.PI / 2;
391
+ group.add(yzRing);
392
+ break;
393
+ }
394
+ case 'capsule': {
395
+ const r = dims.radius!;
396
+ const hh = dims.halfHeight!;
397
+ const topRing = new THREE.Line(createCircleGeometry(r), mat);
398
+ topRing.rotation.x = -Math.PI / 2;
399
+ topRing.position.y = hh;
400
+ group.add(topRing);
401
+ const botRing = new THREE.Line(createCircleGeometry(r), mat);
402
+ botRing.rotation.x = -Math.PI / 2;
403
+ botRing.position.y = -hh;
404
+ group.add(botRing);
405
+ for (const [x, z] of [
406
+ [r, 0],
407
+ [-r, 0],
408
+ [0, r],
409
+ [0, -r],
410
+ ] as [number, number][]) {
411
+ const lineGeo = new THREE.BufferGeometry().setFromPoints([
412
+ new THREE.Vector3(x, -hh, z),
413
+ new THREE.Vector3(x, hh, z),
414
+ ]);
415
+ group.add(new THREE.Line(lineGeo, mat));
416
+ }
417
+ group.add(new THREE.Line(createSemiArcGeometry(r, hh, 'xz', true), mat));
418
+ group.add(new THREE.Line(createSemiArcGeometry(r, hh, 'yz', true), mat));
419
+ group.add(new THREE.Line(createSemiArcGeometry(r, -hh, 'xz', false), mat));
420
+ group.add(new THREE.Line(createSemiArcGeometry(r, -hh, 'yz', false), mat));
421
+ break;
422
+ }
423
+ default:
424
+ return null;
425
+ }
426
+
427
+ return group.children.length > 0 ? group : null;
428
+ }
429
+
430
+ const colliderGizmo: GizmoEntry = {
431
+ test: (entity) => !!entity.physics?.collider,
432
+ createIcon: () => makeIconSprite('⬡', 'collider'),
433
+ createHelpers(entity, obj) {
434
+ const collider = entity.physics?.collider;
435
+ if (!collider) return [];
436
+
437
+ // T1.2: size the wireframe from the entity's composed WORLD scale (self ×
438
+ // ancestors) via the SAME helper the runtime physics loader uses, so the
439
+ // gizmo always matches the actual Rapier collider. `obj.updateWorldMatrix`
440
+ // forces this entity's (and its ancestors') matrixWorld to be current
441
+ // right now, rather than trusting whatever the last render pass left.
442
+ obj.updateWorldMatrix(true, false);
443
+ const worldScaleVec = obj.getWorldScale(new THREE.Vector3());
444
+ const worldScale: [number, number, number] = [
445
+ worldScaleVec.x,
446
+ worldScaleVec.y,
447
+ worldScaleVec.z,
448
+ ];
449
+
450
+ let dims: ColliderWorldDimensions;
451
+ try {
452
+ dims = computeColliderWorldDimensions(collider, worldScale, entity.name);
453
+ } catch {
454
+ // Invalid configuration (e.g. non-uniform scale on a round collider) —
455
+ // this throws a loud load error at Play time. Don't crash the editor
456
+ // over a config the user hasn't fixed yet; just don't render a
457
+ // misleading shape.
458
+ return [];
459
+ }
460
+
461
+ const helper = createColliderHelper(collider, dims);
462
+ if (!helper) return [];
463
+
464
+ // `helper` is added as a CHILD of `obj` below (applyEntityGizmos), so
465
+ // Three's normal hierarchy would otherwise apply `obj`'s (self + ancestor)
466
+ // world scale a SECOND time on top of the world-scaled dimensions just
467
+ // baked into `dims` above. Cancel that inherited scale here so the
468
+ // dimensions above are the only place scale is applied (avoid
469
+ // double-scaling).
470
+ if (worldScaleVec.x !== 0 && worldScaleVec.y !== 0 && worldScaleVec.z !== 0) {
471
+ helper.scale.set(1 / worldScaleVec.x, 1 / worldScaleVec.y, 1 / worldScaleVec.z);
472
+ }
473
+
474
+ // R1c: render the wireframe at the collider's WORLD-SCALED offset (the
475
+ // runtime physics loader bakes `offset * worldScale` into the Rapier
476
+ // collider's translation — see `computeColliderWorldOffset`). `helper` is
477
+ // a CHILD of `obj` (added below by applyEntityGizmos), so Three's normal
478
+ // parent-child matrix composition ALREADY multiplies a child's local
479
+ // `position` by the parent's world scale when computing its world
480
+ // position — unlike geometry vertices (handled above), a child's own
481
+ // `position` is untouched by `helper.scale` (that only affects further
482
+ // descendants' vertices). So setting `helper.position` to the RAW
483
+ // authored (unscaled) offset already renders at the correct world-scaled
484
+ // offset; scaling it again here would double-apply worldScale.
485
+ if (collider.offset) {
486
+ helper.position.set(collider.offset[0], collider.offset[1], collider.offset[2]);
487
+ }
488
+
489
+ return [helper];
490
+ },
491
+ };
492
+
493
+ // ---------------------------------------------------------------------------
494
+ // Spline gizmo
495
+ // ---------------------------------------------------------------------------
496
+
497
+ function createSplineHelper(spline: SceneSpline): THREE.Object3D {
498
+ const group = new THREE.Group();
499
+ if (spline.points.length < 2) return group;
500
+
501
+ const points = spline.points.map((p) => new THREE.Vector3(p[0], p[1], p[2]));
502
+ const curve = new THREE.CatmullRomCurve3(
503
+ points,
504
+ spline.closed ?? false,
505
+ spline.curveType,
506
+ spline.tension ?? 0.5,
507
+ );
508
+ const resolution = spline.resolution ?? 50;
509
+ const samples = curve.getPoints(resolution);
510
+
511
+ const lineGeo = new THREE.BufferGeometry().setFromPoints(samples);
512
+ const lineMat = new THREE.LineBasicMaterial({
513
+ color: 0x00ccff,
514
+ depthTest: false,
515
+ transparent: true,
516
+ opacity: 0.8,
517
+ });
518
+ const line = new THREE.Line(lineGeo, lineMat);
519
+ line.renderOrder = 997;
520
+ group.add(line);
521
+
522
+ const dotGeo = new THREE.SphereGeometry(0.06, 8, 8);
523
+ const dotMat = new THREE.MeshBasicMaterial({
524
+ color: 0xffaa00,
525
+ depthTest: false,
526
+ transparent: true,
527
+ opacity: 0.9,
528
+ });
529
+ for (let i = 0; i < spline.points.length; i++) {
530
+ const dot = new THREE.Mesh(dotGeo, dotMat);
531
+ const p = spline.points[i]!;
532
+ dot.position.set(p[0], p[1], p[2]);
533
+ dot.renderOrder = 998;
534
+ setUserData(dot, 'splineControlPoint', i);
535
+ group.add(dot);
536
+ }
537
+
538
+ return group;
539
+ }
540
+
541
+ const splineGizmo: GizmoEntry = {
542
+ test: (entity) => !!entity.spline,
543
+ createIcon: () => makeIconSprite('〰', 'spline'),
544
+ createHelpers(entity) {
545
+ return [createSplineHelper(entity.spline!)];
546
+ },
547
+ };
548
+
549
+ // ---------------------------------------------------------------------------
550
+ // Registry
551
+ // ---------------------------------------------------------------------------
552
+
553
+ const GIZMO_REGISTRY: GizmoEntry[] = [
554
+ lightGizmo,
555
+ cameraGizmo,
556
+ audioGizmo,
557
+ colliderGizmo,
558
+ splineGizmo,
559
+ ];
560
+
561
+ /**
562
+ * Apply all matching gizmos to an entity object.
563
+ *
564
+ * Adds clickable icon billboards and non-clickable wireframe helpers.
565
+ * Idempotent — skips objects that already have gizmo children.
566
+ */
567
+ export function applyEntityGizmos(obj: THREE.Object3D): void {
568
+ const entity = getUserData(obj, 'entity') as SceneEntity | undefined;
569
+ if (!entity) return;
570
+
571
+ // Idempotent: skip if gizmos already applied
572
+ if (obj.children.some((c) => getUserData(c, 'editorHelper') || getUserData(c, 'editorIcon')))
573
+ return;
574
+
575
+ let added = false;
576
+ for (const entry of GIZMO_REGISTRY) {
577
+ if (!entry.test(entity, obj)) continue;
578
+
579
+ // Icon billboard — clickable, on EDITOR_LAYER
580
+ const icon = entry.createIcon();
581
+ obj.add(icon);
582
+
583
+ // Helper wireframes — visual only, not clickable
584
+ const helpers = entry.createHelpers(entity, obj);
585
+ for (const helper of helpers) {
586
+ setUserData(helper, 'editorHelper', true);
587
+ setUserData(
588
+ helper,
589
+ 'editorHelperType',
590
+ entry === lightGizmo
591
+ ? 'lights'
592
+ : entry === cameraGizmo
593
+ ? 'cameras'
594
+ : entry === audioGizmo
595
+ ? 'audio'
596
+ : entry === colliderGizmo
597
+ ? 'colliders'
598
+ : 'splines',
599
+ );
600
+ helper.traverse((child) => child.layers.set(EDITOR_LAYER));
601
+ obj.add(helper);
602
+ }
603
+ added = true;
604
+ }
605
+
606
+ // Force matrix computation so helpers render at the correct position immediately
607
+ if (added) obj.updateMatrixWorld(true);
608
+ }