@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,369 @@
|
|
|
1
|
+
import { type RenderScope, resolveRenderSettings } from '@engine/render/render-settings';
|
|
2
|
+
import { applyRendererSettings, applyScenePostProcessing } from '@engine/setup/setup-renderer';
|
|
3
|
+
import { EffectComposer } from 'postprocessing';
|
|
4
|
+
import { useEffect, useRef, useSyncExternalStore } from 'react';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import type { CompositeAuthoringAdapter } from '../authoring/composite-authoring-adapter';
|
|
7
|
+
import { mountDesignTimeLayers } from '../authoring/design-time-layers';
|
|
8
|
+
import {
|
|
9
|
+
EDIT_MODE_REBUILD_EVENT,
|
|
10
|
+
exitEditModeAuthoring,
|
|
11
|
+
installEditModeAuthoringForProject,
|
|
12
|
+
} from '../authoring/edit-mode-authoring';
|
|
13
|
+
import { collectState } from '../command-listener';
|
|
14
|
+
import { useEditorStats, useEditorStore } from '../EditorContext';
|
|
15
|
+
import { reportEditorState } from '../editor-api';
|
|
16
|
+
import { connectServerLogs, editorConsole } from '../editor-console';
|
|
17
|
+
import { registerEditorHotkeys } from '../editor-hotkeys';
|
|
18
|
+
import { getEditorMode } from '../editor-mode';
|
|
19
|
+
import type { EditorStore } from '../editor-store';
|
|
20
|
+
import { EditorViewport } from '../editor-viewport';
|
|
21
|
+
import { autoLaunchIngestFromURL, bindIngestMode } from '../ingest-mode';
|
|
22
|
+
import { loadInitialScene } from '../initial-scene';
|
|
23
|
+
import { setupNavMeshHandlers } from '../navmesh-handler';
|
|
24
|
+
import { bindPlayMode } from '../play-mode';
|
|
25
|
+
import { resolvePreviewBoneAttachments } from '../scene-sync';
|
|
26
|
+
import { CameraInfo } from './CameraInfo';
|
|
27
|
+
import { CommandPalette } from './CommandPalette';
|
|
28
|
+
import { NetworkSection } from './NetworkSection';
|
|
29
|
+
import { PlayBar } from './PlayBar';
|
|
30
|
+
import { StatsOverlay } from './StatsOverlay';
|
|
31
|
+
import { ToolStrip } from './Toolbar';
|
|
32
|
+
import { ViewportOverlay } from './ViewportOverlay';
|
|
33
|
+
import { WorldSelectionOverlay } from './WorldSelectionOverlay';
|
|
34
|
+
|
|
35
|
+
function tickEditorAnimations(objectMap: Map<string, THREE.Object3D>, dt: number): void {
|
|
36
|
+
resolvePreviewBoneAttachments(objectMap);
|
|
37
|
+
for (const obj of objectMap.values()) {
|
|
38
|
+
const mixer = obj.userData['_animMixer'] as THREE.AnimationMixer | undefined;
|
|
39
|
+
if (mixer) mixer.update(dt);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function buildEntityTagMap(store: EditorStore): Map<string, string[]> {
|
|
44
|
+
const tagMap = new Map<string, string[]>();
|
|
45
|
+
for (const e of store.allEntities()) {
|
|
46
|
+
if (e.id && e.tags && e.tags.length > 0) tagMap.set(e.id, e.tags);
|
|
47
|
+
}
|
|
48
|
+
return tagMap;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function ViewportPanel() {
|
|
52
|
+
const store = useEditorStore();
|
|
53
|
+
const stats = useEditorStats();
|
|
54
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
55
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const canvas = canvasRef.current!;
|
|
59
|
+
const container = containerRef.current!;
|
|
60
|
+
|
|
61
|
+
// --- Renderer ---
|
|
62
|
+
// antialias is reload-only (renderer construction); shadows/resolutionScale
|
|
63
|
+
// are live and re-applied in rebuildComposer when the environment changes.
|
|
64
|
+
const rendering = store.environment?.rendering as RenderScope | undefined;
|
|
65
|
+
const renderer = new THREE.WebGLRenderer({
|
|
66
|
+
canvas,
|
|
67
|
+
antialias: Boolean(resolveRenderSettings(rendering)['antialias']),
|
|
68
|
+
});
|
|
69
|
+
applyRendererSettings(renderer, rendering, window.devicePixelRatio);
|
|
70
|
+
renderer.setSize(Math.max(1, container.clientWidth), Math.max(1, container.clientHeight));
|
|
71
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
72
|
+
renderer.toneMapping = THREE.NoToneMapping;
|
|
73
|
+
|
|
74
|
+
// --- Post-processing ---
|
|
75
|
+
const composer = new EffectComposer(renderer);
|
|
76
|
+
|
|
77
|
+
// --- Scene ---
|
|
78
|
+
const scene = new THREE.Scene();
|
|
79
|
+
scene.background = new THREE.Color(0xaaaaaa);
|
|
80
|
+
|
|
81
|
+
// --- Bind store to scene ---
|
|
82
|
+
store.bindScene(scene, renderer);
|
|
83
|
+
|
|
84
|
+
// --- Viewport ---
|
|
85
|
+
const viewport = new EditorViewport(canvas, scene, store);
|
|
86
|
+
// Bind the REAL viewport camera + orbit target so store.cameraPose (the
|
|
87
|
+
// `editor.viewport.camera.get` facet, see command-listener.ts's
|
|
88
|
+
// collectState) and the thumbnail-capture path read live data instead of
|
|
89
|
+
// null — previously bindScene's `camera` param was never passed here, so
|
|
90
|
+
// `store.cameraPose` (and _captureThumbnail's camera read) stayed dark.
|
|
91
|
+
store.bindScene(scene, renderer, viewport.batchedRenderer, viewport.camera);
|
|
92
|
+
store.setOrbitTarget(viewport.orbitControls.target);
|
|
93
|
+
// DEV/e2e hook: lets the per-game feature matrix assert the viewport camera
|
|
94
|
+
// reframes (view presets / focus / orbit) on an ingested scene.
|
|
95
|
+
if (import.meta.env.DEV) {
|
|
96
|
+
(window as unknown as Record<string, unknown>)['__vgaiViewport'] = viewport;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --- Composer rebuild ---
|
|
100
|
+
function rebuildComposer(): void {
|
|
101
|
+
// Re-apply live renderer settings (shadows, resolutionScale) so env edits
|
|
102
|
+
// take effect; the post-processing master switch is handled inside
|
|
103
|
+
// applyScenePostProcessing via env.rendering.
|
|
104
|
+
applyRendererSettings(
|
|
105
|
+
renderer,
|
|
106
|
+
store.environment?.rendering as RenderScope | undefined,
|
|
107
|
+
window.devicePixelRatio,
|
|
108
|
+
);
|
|
109
|
+
applyScenePostProcessing(
|
|
110
|
+
composer,
|
|
111
|
+
renderer,
|
|
112
|
+
store.scene ?? scene,
|
|
113
|
+
viewport.camera,
|
|
114
|
+
store.environment,
|
|
115
|
+
store.objectMap,
|
|
116
|
+
buildEntityTagMap(store),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// --- Store subscription → sync viewport ---
|
|
121
|
+
// Rebuild the post-processing chain only when its inputs actually change
|
|
122
|
+
// (environment / scene structure / play-stop swap), tracked by composerVersion.
|
|
123
|
+
// Previously this ran on EVERY notify, so a gizmo drag — which notifies once
|
|
124
|
+
// per pointer-move — reconstructed every EffectPass and recompiled shaders
|
|
125
|
+
// 60×/s, causing severe stutter on any scene with post effects enabled.
|
|
126
|
+
let lastComposerVersion = store.composerVersion;
|
|
127
|
+
const unsubStore = store.subscribe(() => {
|
|
128
|
+
// Swap viewport scene if the store's scene changed (play mode enter/exit)
|
|
129
|
+
const storeScene = store.scene;
|
|
130
|
+
if (storeScene && storeScene !== viewport.currentScene) {
|
|
131
|
+
viewport.setScene(storeScene);
|
|
132
|
+
}
|
|
133
|
+
viewport.objectMap = store.objectMap;
|
|
134
|
+
viewport.syncFromStore();
|
|
135
|
+
if (store.composerVersion !== lastComposerVersion) {
|
|
136
|
+
lastComposerVersion = store.composerVersion;
|
|
137
|
+
rebuildComposer();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// --- Viewport action subscription ---
|
|
142
|
+
const unsubActions = store.onViewportAction((action) => {
|
|
143
|
+
switch (action.type) {
|
|
144
|
+
case 'focus-entity': {
|
|
145
|
+
const obj = store.objectMap.get(action.id);
|
|
146
|
+
if (obj) viewport.focusOn(obj);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'focus-selection': {
|
|
150
|
+
const objects = [...store.selectedEntityIds]
|
|
151
|
+
.map((id) => store.objectMap.get(id))
|
|
152
|
+
.filter((o): o is THREE.Object3D => o !== undefined);
|
|
153
|
+
viewport.focusOnMultiple(objects);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case 'set-view-preset':
|
|
157
|
+
viewport.setViewPreset(action.preset);
|
|
158
|
+
break;
|
|
159
|
+
case 'set-camera-pose':
|
|
160
|
+
viewport.setPose(action.position, action.target, action.fov);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// --- Hotkeys ---
|
|
166
|
+
const unregisterHotkeys = registerEditorHotkeys(store, viewport, canvas);
|
|
167
|
+
|
|
168
|
+
// --- NavMesh ---
|
|
169
|
+
const disposeNavMesh = setupNavMeshHandlers(store, viewport, scene);
|
|
170
|
+
|
|
171
|
+
// --- Play-mode ---
|
|
172
|
+
bindPlayMode({ store, getViewportCamera: () => viewport.camera });
|
|
173
|
+
bindIngestMode(store);
|
|
174
|
+
|
|
175
|
+
// --- Error capture → editor console ---
|
|
176
|
+
const onError = (e: ErrorEvent) => {
|
|
177
|
+
editorConsole.error(e.message || String(e.error), 'runtime');
|
|
178
|
+
};
|
|
179
|
+
const onUnhandledRejection = (e: PromiseRejectionEvent) => {
|
|
180
|
+
editorConsole.error(`Unhandled promise rejection: ${e.reason}`, 'runtime');
|
|
181
|
+
};
|
|
182
|
+
window.addEventListener('error', onError);
|
|
183
|
+
window.addEventListener('unhandledrejection', onUnhandledRejection);
|
|
184
|
+
|
|
185
|
+
// --- Server logs ---
|
|
186
|
+
connectServerLogs();
|
|
187
|
+
|
|
188
|
+
// --- Resize ---
|
|
189
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
190
|
+
// Docking, HMR, and tab switches can briefly collapse the panel to 0x0.
|
|
191
|
+
// Three's EffectComposer allocates incomplete render targets at that
|
|
192
|
+
// size, then floods the console when the shared RAF renders the frame.
|
|
193
|
+
const w = Math.max(1, container.clientWidth);
|
|
194
|
+
const h = Math.max(1, container.clientHeight);
|
|
195
|
+
renderer.setSize(w, h);
|
|
196
|
+
composer.setSize(w, h);
|
|
197
|
+
viewport.resize(w, h);
|
|
198
|
+
});
|
|
199
|
+
resizeObserver.observe(container);
|
|
200
|
+
|
|
201
|
+
// --- RAF loop ---
|
|
202
|
+
let lastTime = performance.now();
|
|
203
|
+
let rafId = 0;
|
|
204
|
+
|
|
205
|
+
function animate(): void {
|
|
206
|
+
rafId = requestAnimationFrame(animate);
|
|
207
|
+
const now = performance.now();
|
|
208
|
+
stats.frameTime = now - lastTime;
|
|
209
|
+
stats.fps = 1000 / stats.frameTime;
|
|
210
|
+
const dt = stats.frameTime / 1000;
|
|
211
|
+
lastTime = now;
|
|
212
|
+
|
|
213
|
+
viewport.update(dt);
|
|
214
|
+
if (store.playState === 'stopped') {
|
|
215
|
+
tickEditorAnimations(store.objectMap, dt);
|
|
216
|
+
}
|
|
217
|
+
viewport.batchedRenderer.update(dt);
|
|
218
|
+
composer.render(dt);
|
|
219
|
+
viewport.renderViewCube(renderer);
|
|
220
|
+
viewport.renderCameraPreview(renderer);
|
|
221
|
+
|
|
222
|
+
stats.drawCalls = renderer.info.render.calls;
|
|
223
|
+
stats.triangles = renderer.info.render.triangles;
|
|
224
|
+
|
|
225
|
+
// Update camera info for CameraInfo panel
|
|
226
|
+
const cam = viewport.camera.position;
|
|
227
|
+
const tgt = viewport.orbitControls.target;
|
|
228
|
+
stats.cameraPosition.x = cam.x;
|
|
229
|
+
stats.cameraPosition.y = cam.y;
|
|
230
|
+
stats.cameraPosition.z = cam.z;
|
|
231
|
+
stats.cameraTarget.x = tgt.x;
|
|
232
|
+
stats.cameraTarget.y = tgt.y;
|
|
233
|
+
stats.cameraTarget.z = tgt.z;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// --- Start rendering, then load the initial scene ---
|
|
237
|
+
// loadInitialScene must run after bindScene so store._scene is set
|
|
238
|
+
// and _rebuildScene can populate the entity tree.
|
|
239
|
+
viewport.objectMap = store.objectMap;
|
|
240
|
+
viewport.syncFromStore();
|
|
241
|
+
rebuildComposer();
|
|
242
|
+
animate();
|
|
243
|
+
// `disposed` guards the unmount-during-fetch race: if cleanup runs before
|
|
244
|
+
// the manifest fetch resolves, tear the just-installed composite down
|
|
245
|
+
// immediately (and never let a late-resolving install leave a stale
|
|
246
|
+
// override behind).
|
|
247
|
+
let disposed = false;
|
|
248
|
+
let editModeComposite: CompositeAuthoringAdapter | undefined;
|
|
249
|
+
let disposeDesignTimeLayers: (() => void) | undefined;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* (A1) fetch `vgai.game.json` and install the edit-mode composite over
|
|
253
|
+
* every declared world, then (B1) composite every OTHER declared world's
|
|
254
|
+
* design-time layer into THIS SAME container (the threejs canvas above
|
|
255
|
+
* keeps its existing slot, untouched). Callable more than once: the
|
|
256
|
+
* initial mount (below) AND (C3) the `'editor:edit-mode-rebuild'`
|
|
257
|
+
* listener — e.g. after the "Add UI Canvas" hierarchy-menu action seeds
|
|
258
|
+
* the scene's first `ui` root, the composite needs rebuilding so its new
|
|
259
|
+
* scene-UI child appears. Tears down any PREVIOUS edit-mode session
|
|
260
|
+
* first, so a rebuild never leaks the old composite/layers.
|
|
261
|
+
*/
|
|
262
|
+
async function installAll(): Promise<void> {
|
|
263
|
+
disposeDesignTimeLayers?.();
|
|
264
|
+
disposeDesignTimeLayers = undefined;
|
|
265
|
+
if (editModeComposite) {
|
|
266
|
+
exitEditModeAuthoring(editModeComposite);
|
|
267
|
+
editModeComposite = undefined;
|
|
268
|
+
}
|
|
269
|
+
const composite = await installEditModeAuthoringForProject(store);
|
|
270
|
+
editModeComposite = composite;
|
|
271
|
+
if (disposed) {
|
|
272
|
+
exitEditModeAuthoring(composite);
|
|
273
|
+
editModeComposite = undefined;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
disposeDesignTimeLayers = mountDesignTimeLayers(container, composite, store);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function onEditModeRebuild(): void {
|
|
280
|
+
void installAll();
|
|
281
|
+
}
|
|
282
|
+
window.addEventListener(EDIT_MODE_REBUILD_EVENT, onEditModeRebuild);
|
|
283
|
+
|
|
284
|
+
// Load the scene, then install the edit-mode composite — BEFORE the
|
|
285
|
+
// ?ingest= auto-launch below, so a later ingest override always wins the
|
|
286
|
+
// shared active-authoring slot deterministically (never raced by this
|
|
287
|
+
// fetch). Then (if the URL carries ?ingest=<id>) auto-launch that
|
|
288
|
+
// unmodified game so the ingest flow is drivable from a plain hosted link.
|
|
289
|
+
loadInitialScene(store)
|
|
290
|
+
.then(() => {
|
|
291
|
+
// B3-followup fix: `connectCommandListener`'s ONE-TIME initial
|
|
292
|
+
// `reportEditorState` (EditorContext.tsx's mount effect) fires before
|
|
293
|
+
// this scene finishes loading — command-listener.ts otherwise only
|
|
294
|
+
// re-reports state after a RELAYED COMMAND, so `editor.hierarchy.
|
|
295
|
+
// inspect`/`editor.viewport.camera.get` would see a stale, empty
|
|
296
|
+
// entityCount/no-camera snapshot forever if no command happens to be
|
|
297
|
+
// sent first. Report once more right after the scene (and thus the
|
|
298
|
+
// camera binding just above) is actually ready — a real state
|
|
299
|
+
// transition, not a fabricated poke. No-ops harmlessly in browser
|
|
300
|
+
// mode (no server to receive it — reportEditorState is best-effort).
|
|
301
|
+
if (getEditorMode() !== 'browser') reportEditorState(collectState(store));
|
|
302
|
+
})
|
|
303
|
+
.then(() => installAll())
|
|
304
|
+
.then(() => autoLaunchIngestFromURL(store));
|
|
305
|
+
|
|
306
|
+
// --- Cleanup ---
|
|
307
|
+
return () => {
|
|
308
|
+
disposed = true;
|
|
309
|
+
cancelAnimationFrame(rafId);
|
|
310
|
+
resizeObserver.disconnect();
|
|
311
|
+
unsubStore();
|
|
312
|
+
unsubActions();
|
|
313
|
+
unregisterHotkeys();
|
|
314
|
+
disposeNavMesh();
|
|
315
|
+
window.removeEventListener(EDIT_MODE_REBUILD_EVENT, onEditModeRebuild);
|
|
316
|
+
disposeDesignTimeLayers?.();
|
|
317
|
+
exitEditModeAuthoring(editModeComposite);
|
|
318
|
+
window.removeEventListener('error', onError);
|
|
319
|
+
window.removeEventListener('unhandledrejection', onUnhandledRejection);
|
|
320
|
+
viewport.dispose();
|
|
321
|
+
// EffectComposer owns input/output WebGLRenderTargets + per-pass resources
|
|
322
|
+
// that renderer.dispose() does NOT free — dispose it to avoid leaking GPU
|
|
323
|
+
// render targets on unmount (incl. React StrictMode's mount/unmount/remount).
|
|
324
|
+
composer.dispose();
|
|
325
|
+
renderer.dispose();
|
|
326
|
+
};
|
|
327
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- runs once on mount
|
|
328
|
+
|
|
329
|
+
// Subscribe to store for overlay visibility
|
|
330
|
+
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<div
|
|
334
|
+
ref={containerRef}
|
|
335
|
+
style={{
|
|
336
|
+
position: 'absolute',
|
|
337
|
+
top: 0,
|
|
338
|
+
left: 0,
|
|
339
|
+
width: '100%',
|
|
340
|
+
height: '100%',
|
|
341
|
+
overflow: 'hidden',
|
|
342
|
+
pointerEvents: 'auto',
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
<canvas
|
|
346
|
+
ref={canvasRef}
|
|
347
|
+
data-testid="editor-canvas"
|
|
348
|
+
aria-hidden="true"
|
|
349
|
+
tabIndex={-1}
|
|
350
|
+
style={{ width: '100%', height: '100%', display: 'block', pointerEvents: 'auto' }}
|
|
351
|
+
/>
|
|
352
|
+
{/* Spec 27 §3 A3 — mounted as a CHILD of THIS SAME containerRef div, the exact
|
|
353
|
+
DOM parent `mountDesignTimeLayers(container, …)` above appends each react/pixi
|
|
354
|
+
world's own `position:absolute; inset:0` layer into, so `RectProvider.rect(id)`'s
|
|
355
|
+
host-relative geometry lines up with this overlay's own coordinate space with no
|
|
356
|
+
correction (see WorldSelectionOverlay.tsx's own doc comment). Self-gates to a
|
|
357
|
+
no-op when the active composite has no rect-capable (DOM) child, so a 3D-only
|
|
358
|
+
project renders nothing extra here. */}
|
|
359
|
+
<WorldSelectionOverlay />
|
|
360
|
+
<PlayBar />
|
|
361
|
+
<ToolStrip />
|
|
362
|
+
<ViewportOverlay />
|
|
363
|
+
{store.showStats && <StatsOverlay />}
|
|
364
|
+
{store.playState !== 'stopped' && <NetworkSection />}
|
|
365
|
+
<CameraInfo />
|
|
366
|
+
{store.commandPaletteOpen && <CommandPalette onClose={() => store.closeCommandPalette()} />}
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorldContextMenu — spec 27 §6 D3.a (docs/unified-world-editor/
|
|
3
|
+
* 27-visual-react-editing.md) right-click canvas context menu: Duplicate /
|
|
4
|
+
* Wrap / Unwrap / Delete / Select-Parent, plus an Insert-child ▸ submenu
|
|
5
|
+
* (D3.e) when the owning adapter reports `structure.creatableKinds`. Styled
|
|
6
|
+
* after (but a SEPARATE component from) `GameHierarchy.tsx`'s hierarchy-row
|
|
7
|
+
* `ContextMenu` — that one opens from a right-click on a HIERARCHY ROW; this
|
|
8
|
+
* one opens from a right-click on the CANVAS overlay, resolving its target
|
|
9
|
+
* node via `adapter.pickable` (see `WorldSelectionOverlay`'s `onContextMenu`
|
|
10
|
+
* handler) rather than a row's own known id. Both ultimately drive the SAME
|
|
11
|
+
* `AuthoringAdapter.structure` contract, so the two menus intentionally look
|
|
12
|
+
* alike.
|
|
13
|
+
*
|
|
14
|
+
* Rule zero: this file talks ONLY to the `AuthoringAdapter` contract (via the
|
|
15
|
+
* owner-routing helpers in `world-overlay-gestures.ts` — `structureForId`,
|
|
16
|
+
* not a concrete adapter cast) + `hierarchy`/`selection`, which ARE
|
|
17
|
+
* merged/forwarded on `CompositeAuthoringAdapter` (unlike `rects`/`boxEdit`/
|
|
18
|
+
* `text`/`structure`'s OWNER-specific reads here). No `UIEditStore`/
|
|
19
|
+
* `SceneEntity`/`THREE.`/`elementFromPoint`.
|
|
20
|
+
*/
|
|
21
|
+
import type { AuthoringAdapter } from '@engine/adapter';
|
|
22
|
+
import { useEffect, useRef, useState } from 'react';
|
|
23
|
+
import { createPortal } from 'react-dom';
|
|
24
|
+
import { structureForId } from './world-overlay-gestures';
|
|
25
|
+
|
|
26
|
+
export interface WorldContextMenuState {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
nodeId: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface WorldContextMenuProps {
|
|
33
|
+
adapter: AuthoringAdapter;
|
|
34
|
+
state: WorldContextMenuState;
|
|
35
|
+
onClose: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const MENU_BG = '#2c2c2c';
|
|
39
|
+
const MENU_BORDER = '#444';
|
|
40
|
+
const ITEM_HOVER = '#3a3a3a';
|
|
41
|
+
|
|
42
|
+
function menuItemStyle(color?: string): React.CSSProperties {
|
|
43
|
+
return { padding: '5px 12px', cursor: 'pointer', color: color ?? '#c5c8ce' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hoverHandlers(): {
|
|
47
|
+
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
48
|
+
onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
49
|
+
} {
|
|
50
|
+
return {
|
|
51
|
+
onMouseEnter: (e) => {
|
|
52
|
+
(e.currentTarget as HTMLDivElement).style.background = ITEM_HOVER;
|
|
53
|
+
},
|
|
54
|
+
onMouseLeave: (e) => {
|
|
55
|
+
(e.currentTarget as HTMLDivElement).style.background = 'transparent';
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The canvas right-click menu (D3.a) — a pickable node's Duplicate/Wrap/
|
|
61
|
+
* Unwrap/Delete/Select-Parent, capability-gated on the owning adapter's
|
|
62
|
+
* `structure` provider (absent for an ingested `react-dom` world — no
|
|
63
|
+
* structural items render at all, only Select-Parent, which needs only
|
|
64
|
+
* `hierarchy`/`selection`). */
|
|
65
|
+
export function WorldContextMenu({
|
|
66
|
+
adapter,
|
|
67
|
+
state,
|
|
68
|
+
onClose,
|
|
69
|
+
}: WorldContextMenuProps): React.ReactNode {
|
|
70
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
71
|
+
const [insertOpen, setInsertOpen] = useState(false);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const handler = (e: MouseEvent): void => {
|
|
75
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) onClose();
|
|
76
|
+
};
|
|
77
|
+
const keyHandler = (e: KeyboardEvent): void => {
|
|
78
|
+
if (e.key === 'Escape') onClose();
|
|
79
|
+
};
|
|
80
|
+
document.addEventListener('mousedown', handler);
|
|
81
|
+
document.addEventListener('keydown', keyHandler);
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener('mousedown', handler);
|
|
84
|
+
document.removeEventListener('keydown', keyHandler);
|
|
85
|
+
};
|
|
86
|
+
}, [onClose]);
|
|
87
|
+
|
|
88
|
+
const { nodeId } = state;
|
|
89
|
+
const node = adapter.hierarchy.node(nodeId);
|
|
90
|
+
if (!node) return null;
|
|
91
|
+
|
|
92
|
+
const structure = structureForId(adapter, nodeId);
|
|
93
|
+
const creatableKinds = structure?.creatableKinds?.(nodeId) ?? [];
|
|
94
|
+
const parentId = node.parentId;
|
|
95
|
+
|
|
96
|
+
const items: { testId: string; label: string; action: () => void; color?: string }[] = [];
|
|
97
|
+
if (parentId !== null) {
|
|
98
|
+
items.push({
|
|
99
|
+
testId: 'world-context-menu-select-parent',
|
|
100
|
+
label: 'Select Parent',
|
|
101
|
+
action: () => {
|
|
102
|
+
adapter.selection?.set([parentId]);
|
|
103
|
+
onClose();
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (structure) {
|
|
108
|
+
items.push({
|
|
109
|
+
testId: 'world-context-menu-duplicate',
|
|
110
|
+
label: 'Duplicate',
|
|
111
|
+
action: () => {
|
|
112
|
+
structure.duplicate(nodeId);
|
|
113
|
+
onClose();
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (structure.wrap) {
|
|
117
|
+
items.push({
|
|
118
|
+
testId: 'world-context-menu-wrap',
|
|
119
|
+
label: 'Wrap',
|
|
120
|
+
action: () => {
|
|
121
|
+
structure.wrap!(nodeId);
|
|
122
|
+
onClose();
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (structure.unwrap) {
|
|
127
|
+
items.push({
|
|
128
|
+
testId: 'world-context-menu-unwrap',
|
|
129
|
+
label: 'Unwrap',
|
|
130
|
+
action: () => {
|
|
131
|
+
structure.unwrap!(nodeId);
|
|
132
|
+
onClose();
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Portaled straight to `document.body` (spec-27 §8 pan-seam fix): this
|
|
139
|
+
// component renders as a DOM descendant of `WorldSelectionOverlay`'s outer
|
|
140
|
+
// wrapper, which space-pan gives a live `transform` when pan≠0. A non-`none`
|
|
141
|
+
// transform on an ancestor becomes the CSS containing block for a
|
|
142
|
+
// `position: fixed` descendant, so — un-portaled — this menu's `left`/`top`
|
|
143
|
+
// (raw `clientX`/`clientY` from the triggering right-click, see
|
|
144
|
+
// `WorldSelectionOverlay`'s `onContextMenu`) would resolve against the
|
|
145
|
+
// transformed wrapper instead of the viewport once panned, displacing the
|
|
146
|
+
// menu away from the cursor. Portaling to `document.body` — which never
|
|
147
|
+
// carries that transform — restores `position: fixed`'s normal
|
|
148
|
+
// viewport-relative meaning regardless of pan. `menuRef` still points at
|
|
149
|
+
// the REAL DOM node either way (a portal only changes WHERE React inserts
|
|
150
|
+
// the subtree, not the node itself), so the outside-click/Escape-to-close
|
|
151
|
+
// handlers below — native `document` listeners doing DOM `.contains()`
|
|
152
|
+
// checks — are unaffected by the portal.
|
|
153
|
+
return createPortal(
|
|
154
|
+
<div
|
|
155
|
+
ref={menuRef}
|
|
156
|
+
data-testid="world-context-menu"
|
|
157
|
+
style={{
|
|
158
|
+
position: 'fixed',
|
|
159
|
+
left: state.x,
|
|
160
|
+
top: state.y,
|
|
161
|
+
background: MENU_BG,
|
|
162
|
+
border: `1px solid ${MENU_BORDER}`,
|
|
163
|
+
borderRadius: 4,
|
|
164
|
+
padding: '4px 0',
|
|
165
|
+
zIndex: 1000,
|
|
166
|
+
minWidth: 150,
|
|
167
|
+
maxWidth: 260,
|
|
168
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
|
|
169
|
+
fontSize: 12,
|
|
170
|
+
color: '#c5c8ce',
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
{items.map((item) => (
|
|
174
|
+
<div
|
|
175
|
+
key={item.testId}
|
|
176
|
+
data-testid={item.testId}
|
|
177
|
+
role="menuitem"
|
|
178
|
+
tabIndex={0}
|
|
179
|
+
onClick={item.action}
|
|
180
|
+
onKeyDown={(e) => {
|
|
181
|
+
if (e.key === 'Enter') item.action();
|
|
182
|
+
}}
|
|
183
|
+
style={menuItemStyle(item.color)}
|
|
184
|
+
{...hoverHandlers()}
|
|
185
|
+
>
|
|
186
|
+
{item.label}
|
|
187
|
+
</div>
|
|
188
|
+
))}
|
|
189
|
+
{/* D3.e — Insert-child submenu, gated on the owning adapter's
|
|
190
|
+
`structure.creatableKinds` (react-world only today; the ingested
|
|
191
|
+
react-dom adapter has no `structure` at all, so `structure` is
|
|
192
|
+
`null` here and this whole block is skipped). */}
|
|
193
|
+
{structure && creatableKinds.length > 0 && (
|
|
194
|
+
<div>
|
|
195
|
+
<div
|
|
196
|
+
data-testid="world-context-menu-insert-child"
|
|
197
|
+
role="menuitem"
|
|
198
|
+
tabIndex={0}
|
|
199
|
+
onClick={() => setInsertOpen((v) => !v)}
|
|
200
|
+
onKeyDown={(e) => {
|
|
201
|
+
if (e.key === 'Enter') setInsertOpen((v) => !v);
|
|
202
|
+
}}
|
|
203
|
+
style={menuItemStyle()}
|
|
204
|
+
{...hoverHandlers()}
|
|
205
|
+
>
|
|
206
|
+
Insert child ▸
|
|
207
|
+
</div>
|
|
208
|
+
{insertOpen &&
|
|
209
|
+
creatableKinds.map((k) => (
|
|
210
|
+
<div
|
|
211
|
+
key={k.kind}
|
|
212
|
+
data-testid={`world-context-menu-insert-child-${k.kind}`}
|
|
213
|
+
role="menuitem"
|
|
214
|
+
tabIndex={0}
|
|
215
|
+
onClick={() => {
|
|
216
|
+
structure.create(k.kind, nodeId);
|
|
217
|
+
onClose();
|
|
218
|
+
}}
|
|
219
|
+
onKeyDown={(e) => {
|
|
220
|
+
if (e.key === 'Enter') {
|
|
221
|
+
structure.create(k.kind, nodeId);
|
|
222
|
+
onClose();
|
|
223
|
+
}
|
|
224
|
+
}}
|
|
225
|
+
style={{ padding: '5px 12px 5px 24px', cursor: 'pointer' }}
|
|
226
|
+
{...hoverHandlers()}
|
|
227
|
+
>
|
|
228
|
+
{k.label}
|
|
229
|
+
</div>
|
|
230
|
+
))}
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
{structure && (
|
|
234
|
+
<div
|
|
235
|
+
data-testid="world-context-menu-delete"
|
|
236
|
+
role="menuitem"
|
|
237
|
+
tabIndex={0}
|
|
238
|
+
onClick={() => {
|
|
239
|
+
structure.remove(nodeId);
|
|
240
|
+
onClose();
|
|
241
|
+
}}
|
|
242
|
+
onKeyDown={(e) => {
|
|
243
|
+
if (e.key === 'Enter') {
|
|
244
|
+
structure.remove(nodeId);
|
|
245
|
+
onClose();
|
|
246
|
+
}
|
|
247
|
+
}}
|
|
248
|
+
style={menuItemStyle('#ff6b6b')}
|
|
249
|
+
{...hoverHandlers()}
|
|
250
|
+
>
|
|
251
|
+
Delete
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
</div>,
|
|
255
|
+
document.body,
|
|
256
|
+
);
|
|
257
|
+
}
|