@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.
- package/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- 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
|
+
}
|