@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,117 @@
1
+ /**
2
+ * Inspector section-contribution registry (C2,
3
+ * docs/unified-world-editor/26-unified-editor-spec.md §4 C2) — the
4
+ * PERMANENT, sanctioned adapter-module seam for contributing rich React
5
+ * sections to the ONE inspector shell (`components/Inspector.tsx`, formerly
6
+ * `IngestPanels.tsx`'s `GenericInspector`). Mirrors
7
+ * `hierarchy-menu-registry.ts`'s `register(match, …)` / `get(ctx)` /
8
+ * `__resetForTest` pattern one-for-one — same "test the pure logic
9
+ * headlessly" convention (no jsdom in this repo's default `vitest.config.ts`
10
+ * `environment: 'node'`).
11
+ *
12
+ * The generic property grid (`PropertyDescriptor.group` →
13
+ * `inspector-property-grouping.ts`'s `groupProperties()` →
14
+ * `ingest-group-<id>` sub-sections, rendered by the shell) stays untouched
15
+ * and composes WITH this registry: registered sections render AFTER the
16
+ * grid, as siblings. A registration may `suppresses` one or more built-in
17
+ * shell blocks it fully replaces (e.g. the first-party threejs adapter's
18
+ * rich `ComponentsSection` suppresses the shell's thin `components` grid
19
+ * block) so a matched selection never double-renders the same affordance —
20
+ * see `authoring/first-party-inspector/register.tsx` for the sanctioned
21
+ * first-party usage and `authoring/ui-inspector-sections.tsx` for the UI
22
+ * adapter's.
23
+ *
24
+ * Engine stays React-free (D6): this module, every registration, and every
25
+ * `Section` component live in the editor package only.
26
+ */
27
+
28
+ import type { AuthoringAdapter, EditorNode } from '@engine/adapter';
29
+ import type { ComponentType } from 'react';
30
+
31
+ /** Built-in shell blocks a matched registration may suppress (see
32
+ * `components/Inspector.tsx`'s render order for exactly what each covers). */
33
+ export type InspectorBuiltin =
34
+ | 'header' // name input + kind line
35
+ | 'transform' // Position/Scale AxisRows
36
+ | 'properties' // ungrouped + grouped (`ingest-group-*`) descriptor grid
37
+ | 'components' // the C1 components block (incl. its N/A fallback)
38
+ | 'stories' // the D4/B3 stories block
39
+ | 'environment'; // the no-selection ingest-env branch
40
+
41
+ export interface InspectorSectionProps {
42
+ readonly adapter: AuthoringAdapter;
43
+ /** Selected node id; `null` in the no-selection (environment) state. */
44
+ readonly nodeId: string | null;
45
+ }
46
+
47
+ export interface InspectorSectionRegistration {
48
+ /** `node` is `null` when nothing is selected (environment state). */
49
+ readonly match: (node: EditorNode | null, adapter: AuthoringAdapter) => boolean;
50
+ readonly Section: ComponentType<InspectorSectionProps>;
51
+ /** Built-in shell blocks this registration replaces (default: none). */
52
+ readonly suppresses?: readonly InspectorBuiltin[];
53
+ /** Optional panel-title override — first non-empty one (in registration
54
+ * order, among MATCHED registrations) wins over the shell's default. */
55
+ readonly panelName?: string;
56
+ }
57
+
58
+ const registrations: InspectorSectionRegistration[] = [];
59
+
60
+ // --- Change notification (W3, docs/DATA-TOOLS-DESIGN.md §7) ---------------
61
+ // First-party registrations all happen at import time (main.tsx side
62
+ // effects), before React ever renders — nothing needed to observe them. W3's
63
+ // project inspector TOOLS register asynchronously (folder scan) and
64
+ // re-register on HMR, while a selection may already be showing — so the one
65
+ // inspector shell subscribes here (`useSyncExternalStore`-shaped, mirroring
66
+ // `tool-loader.ts`'s registry store) and re-renders on any (un)registration.
67
+ let _version = 0;
68
+ const _listeners = new Set<() => void>();
69
+
70
+ function notifyRegistryChanged(): void {
71
+ _version++;
72
+ for (const fn of _listeners) fn();
73
+ }
74
+
75
+ /** Subscribe to (un)registrations. Returns an unsubscribe function. */
76
+ export function subscribeInspectorSectionRegistry(fn: () => void): () => void {
77
+ _listeners.add(fn);
78
+ return () => {
79
+ _listeners.delete(fn);
80
+ };
81
+ }
82
+
83
+ /** Monotonic change counter — the `getSnapshot` for `useSyncExternalStore`. */
84
+ export function inspectorSectionRegistryVersion(): number {
85
+ return _version;
86
+ }
87
+
88
+ /**
89
+ * Register a section contribution. `match` decides whether this
90
+ * registration applies to a given selection (or the no-selection state);
91
+ * `Section` renders when it does. Returns an unregister function.
92
+ */
93
+ export function registerInspectorSections(reg: InspectorSectionRegistration): () => void {
94
+ registrations.push(reg);
95
+ notifyRegistryChanged();
96
+ return () => {
97
+ const idx = registrations.indexOf(reg);
98
+ if (idx >= 0) {
99
+ registrations.splice(idx, 1);
100
+ notifyRegistryChanged();
101
+ }
102
+ };
103
+ }
104
+
105
+ /** Every registration whose `match` applies to `node`/`adapter`, in registration order. */
106
+ export function matchedInspectorSections(
107
+ node: EditorNode | null,
108
+ adapter: AuthoringAdapter,
109
+ ): InspectorSectionRegistration[] {
110
+ return registrations.filter((r) => r.match(node, adapter));
111
+ }
112
+
113
+ /** Test-only reset (mirrors `__resetHierarchyMenuRegistryForTest`). */
114
+ export function __resetInspectorSectionRegistryForTest(): void {
115
+ registrations.length = 0;
116
+ notifyRegistryChanged();
117
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Interactive-edit (undo-gesture) scope — the ONE thing the shared input
3
+ * primitives (`components/primitives/NumberInput.tsx` / `ColorInput.tsx`)
4
+ * need from the editor: "a continuous gesture (scrub / color drag) starts /
5
+ * ends now, coalesce it into a single undo step".
6
+ *
7
+ * Split out of `EditorContext.tsx` for W3 of `docs/DATA-TOOLS-DESIGN.md`
8
+ * (§3.4): the widget kit is a public export (`@editor/widgets`) that
9
+ * scaffolded PROJECTS typecheck — importing `EditorContext` (and through it
10
+ * `EditorStore` and most of the editor) from a widget would drag the whole
11
+ * editor source graph into every project's `tsc`. This module's closure is
12
+ * react-only; `EditorProvider` bridges it to the real store
13
+ * (`beginInteractiveEdit`/`endInteractiveEdit`).
14
+ *
15
+ * Widgets consume it optionally (`useInteractiveEditScope()` returns `null`
16
+ * outside a provider — e.g. the widget gallery, or a project tool rendered
17
+ * in some future host): no scope simply means no undo coalescing.
18
+ */
19
+
20
+ import { createContext, useContext } from 'react';
21
+
22
+ /** Begin/end a coalesced interactive edit gesture (one undo step). Both are
23
+ * idempotent in the editor's implementation — safe to call defensively. */
24
+ export interface InteractiveEditScope {
25
+ begin(): void;
26
+ end(): void;
27
+ }
28
+
29
+ export const InteractiveEditScopeContext = createContext<InteractiveEditScope | null>(null);
30
+
31
+ /** The ambient gesture scope, or `null` when no host provides one. */
32
+ export function useInteractiveEditScope(): InteractiveEditScope | null {
33
+ return useContext(InteractiveEditScopeContext);
34
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,23 @@
1
+ import { registerSceneUIRenderer } from '@vgai/scene-ui/register-scene-ui';
2
+ import { createRoot } from 'react-dom/client';
3
+ // Adapter-module contribution-seam registrations (side-effect imports only):
4
+ // the permanent escape hatch for first-party UI that isn't yet contract-
5
+ // expressible (A2's hierarchy-menu-registry.ts: prefab Unpack/Open/Save-as
6
+ // context-menu items, and (C3) the UI adapter's "Add UI Canvas" item; C2's
7
+ // inspector-section-registry.ts: the first-party threejs adapter's rich
8
+ // inspector sections, and the UI adapter's own — registered here so both are
9
+ // live for the unified editor's scene-UI child (C3). `scene-ui-mode/ui-prefab-hierarchy-
10
+ // menu.ts` (E1.c) is the scene-UI "Save as Prefab..." counterpart.
11
+ import './authoring/first-party-hierarchy-menu';
12
+ import './authoring/first-party-inspector/register';
13
+ import './authoring/react-inspector-section';
14
+ import './authoring/ui-hierarchy-menu';
15
+ import './authoring/ui-inspector-sections';
16
+ import './scene-ui-mode/ui-prefab-hierarchy-menu';
17
+ import { AppRoot } from './components/AppRoot';
18
+
19
+ // Wire the React UI runtime into the engine's scene-UI bridge so a scene's `ui`
20
+ // tree renders at game-time (B1) — the engine itself stays React-free.
21
+ registerSceneUIRenderer();
22
+
23
+ createRoot(document.getElementById('editor-root')!).render(<AppRoot />);
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Manifest-backed project detection for the play path — T3.3 slice 2 part C
3
+ * (docs/CLI-ON-FOLDER-DESIGN.md §1D).
4
+ *
5
+ * The dev server serves the project's RAW `vgai.game.json` at `/vgai.game.json`
6
+ * (editor-server.ts's project-file-serving middleware, mirroring how it
7
+ * special-cases `/project.json`) with no view synthesis — the browser parses
8
+ * it itself through the PURE half of the manifest loader (`loadGameManifest`,
9
+ * `@engine/manifest/load`), never `load-file.ts` (that needs `node:fs`, which
10
+ * has no place in a browser bundle).
11
+ */
12
+
13
+ import { loadGameManifest, type ResolvedGameManifest } from '@engine/manifest/load';
14
+
15
+ /**
16
+ * Fetch + parse the current project's manifest, or `null` if it has none
17
+ * (legacy `project.json`-only project — the raw-manifest route 404s, same as
18
+ * any other missing project-root file per editor-server.ts). A manifest that
19
+ * EXISTS but fails validation still throws (same fail-loud contract
20
+ * `loadGameManifest` always has) — never silently falls back.
21
+ */
22
+ export async function fetchGameManifest(): Promise<ResolvedGameManifest | null> {
23
+ const res = await fetch('/vgai.game.json');
24
+ if (!res.ok) return null;
25
+ return loadGameManifest(await res.json());
26
+ }
27
+
28
+ /**
29
+ * A1 (edit mode, defect 2): fetch the RAW, UN-validated `vgai.game.json` JSON
30
+ * — or `null` when the project genuinely has none (the route 404s). Unlike
31
+ * {@link fetchGameManifest}, this NEVER throws on a schema violation: edit mode
32
+ * must tolerate a manifest with ONE bad world (e.g. an unknown `kind`) by
33
+ * surfacing that world as an error node (#18) while the VALID worlds declared
34
+ * alongside it still open — strict `loadGameManifest` would reject the whole
35
+ * file and make every declared world vanish. The caller (`edit-mode-authoring
36
+ * .ts`) reads `worlds[]` leniently; a genuinely absent manifest (this returns
37
+ * `null`) is the only case that synthesizes a single-world game.
38
+ */
39
+ export async function fetchRawGameManifest(): Promise<unknown | null> {
40
+ const res = await fetch('/vgai.game.json');
41
+ if (!res.ok) return null;
42
+ return res.json();
43
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Offscreen Three.js renderer that generates thumbnail images for .glb/.gltf models.
3
+ *
4
+ * Usage:
5
+ * const renderer = getModelThumbnailRenderer();
6
+ * const dataUrl = await renderer.render('/assets/models/Robot.glb');
7
+ * // dataUrl is a PNG data-URL string suitable for <img src="...">
8
+ *
9
+ * - Singleton: one renderer is shared across all callers.
10
+ * - Deduplicating: concurrent requests for the same URL share one render pass.
11
+ * - Cached: once rendered, the data URL is returned immediately.
12
+ * - Self-cleaning: each loaded GLTF is disposed after the thumbnail is captured.
13
+ */
14
+
15
+ import { gltfLoader } from '@engine/loader';
16
+ import * as THREE from 'three';
17
+
18
+ const SIZE = 128;
19
+
20
+ let instance: ModelThumbnailRenderer | null = null;
21
+
22
+ class ModelThumbnailRenderer {
23
+ private renderer: THREE.WebGLRenderer;
24
+ private scene: THREE.Scene;
25
+ private camera: THREE.PerspectiveCamera;
26
+ private cache = new Map<string, string>();
27
+ private pending = new Map<string, Promise<string>>();
28
+ // Shared, DRACO-wired GLTFLoader (A2 / CB2). One-off previews call `.load()`
29
+ // directly (no caching/cloning) and dispose the result after capture, which is
30
+ // safe — each parse is independent of the shared asset-loaders GLTF cache.
31
+ private loader = gltfLoader;
32
+
33
+ constructor() {
34
+ const canvas = document.createElement('canvas');
35
+ canvas.width = SIZE;
36
+ canvas.height = SIZE;
37
+
38
+ this.renderer = new THREE.WebGLRenderer({
39
+ canvas,
40
+ alpha: true,
41
+ antialias: true,
42
+ preserveDrawingBuffer: true,
43
+ });
44
+ this.renderer.setSize(SIZE, SIZE);
45
+ this.renderer.setPixelRatio(1);
46
+ this.renderer.outputColorSpace = THREE.SRGBColorSpace;
47
+ this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
48
+ this.renderer.toneMappingExposure = 1.0;
49
+
50
+ this.scene = new THREE.Scene();
51
+
52
+ // Soft ambient fill
53
+ this.scene.add(new THREE.AmbientLight(0xffffff, 0.8));
54
+
55
+ // Key light (upper-right-front)
56
+ const key = new THREE.DirectionalLight(0xffffff, 1.5);
57
+ key.position.set(3, 5, 4);
58
+ this.scene.add(key);
59
+
60
+ // Fill light (lower-left, dimmer, warm)
61
+ const fill = new THREE.DirectionalLight(0xffeedd, 0.4);
62
+ fill.position.set(-3, -1, 2);
63
+ this.scene.add(fill);
64
+
65
+ this.camera = new THREE.PerspectiveCamera(35, 1, 0.01, 1000);
66
+ }
67
+
68
+ /** Render a thumbnail for the given model URL. Returns a PNG data-URL. */
69
+ async render(url: string): Promise<string> {
70
+ const cached = this.cache.get(url);
71
+ if (cached) return cached;
72
+
73
+ // Deduplicate concurrent requests for the same URL
74
+ const inflight = this.pending.get(url);
75
+ if (inflight) return inflight;
76
+
77
+ const promise = this._render(url);
78
+ this.pending.set(url, promise);
79
+ try {
80
+ return await promise;
81
+ } finally {
82
+ this.pending.delete(url);
83
+ }
84
+ }
85
+
86
+ private async _render(url: string): Promise<string> {
87
+ const group = await new Promise<THREE.Group>((resolve, reject) => {
88
+ this.loader.load(url, (gltf) => resolve(gltf.scene), undefined, reject);
89
+ });
90
+
91
+ this.scene.add(group);
92
+
93
+ // Auto-frame the camera around the model
94
+ const box = new THREE.Box3().setFromObject(group);
95
+ const center = box.getCenter(new THREE.Vector3());
96
+ const size = box.getSize(new THREE.Vector3());
97
+ const maxDim = Math.max(size.x, size.y, size.z) || 1;
98
+ const fovRad = (this.camera.fov * Math.PI) / 180;
99
+ const dist = (maxDim / (2 * Math.tan(fovRad / 2))) * 1.3; // 1.3× padding
100
+
101
+ // Position camera at a 3/4 angle (slightly above, rotated 30° from front)
102
+ this.camera.position.set(
103
+ center.x + dist * 0.65,
104
+ center.y + dist * 0.45,
105
+ center.z + dist * 0.65,
106
+ );
107
+ this.camera.lookAt(center);
108
+ this.camera.near = dist * 0.01;
109
+ this.camera.far = dist * 20;
110
+ this.camera.updateProjectionMatrix();
111
+
112
+ // Render
113
+ this.renderer.setClearColor(0x000000, 0);
114
+ this.renderer.clear();
115
+ this.renderer.render(this.scene, this.camera);
116
+ const dataUrl = this.renderer.domElement.toDataURL('image/png');
117
+
118
+ // Clean up the loaded GLTF to free GPU memory
119
+ this.scene.remove(group);
120
+ group.traverse((obj) => {
121
+ const mesh = obj as THREE.Mesh;
122
+ if (mesh.geometry) mesh.geometry.dispose();
123
+ if (mesh.material) {
124
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
125
+ for (const mat of materials) {
126
+ // Dispose any textures attached to the material
127
+ if ('map' in mat && mat.map instanceof THREE.Texture) mat.map.dispose();
128
+ if ('normalMap' in mat && mat.normalMap instanceof THREE.Texture) mat.normalMap.dispose();
129
+ if ('emissiveMap' in mat && mat.emissiveMap instanceof THREE.Texture)
130
+ mat.emissiveMap.dispose();
131
+ if ('aoMap' in mat && mat.aoMap instanceof THREE.Texture) mat.aoMap.dispose();
132
+ if ('roughnessMap' in mat && mat.roughnessMap instanceof THREE.Texture)
133
+ mat.roughnessMap.dispose();
134
+ if ('metalnessMap' in mat && mat.metalnessMap instanceof THREE.Texture)
135
+ mat.metalnessMap.dispose();
136
+ mat.dispose();
137
+ }
138
+ }
139
+ });
140
+
141
+ this.cache.set(url, dataUrl);
142
+ return dataUrl;
143
+ }
144
+
145
+ dispose() {
146
+ this.renderer.dispose();
147
+ this.cache.clear();
148
+ instance = null;
149
+ }
150
+ }
151
+
152
+ /** Get (or create) the shared thumbnail renderer. */
153
+ export function getModelThumbnailRenderer(): ModelThumbnailRenderer {
154
+ if (!instance) instance = new ModelThumbnailRenderer();
155
+ return instance;
156
+ }