@volter/game-editor 0.5.65
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/BUNDLED_NOTICES +11228 -0
- package/LICENSE +686 -0
- package/LICENSE-BLENDER +1409 -0
- package/LICENSE-MCP +21 -0
- package/NOTICE +21 -0
- package/catalog/entries/audio.json +14 -0
- package/catalog/entries/bake.json +36 -0
- package/catalog/entries/blender.json +17 -0
- package/catalog/entries/react-root.json +24 -0
- package/catalog/entries/reflections.json +29 -0
- package/catalog/entries/static-batch.json +25 -0
- package/catalog/project-source/LICENSE +37 -0
- package/catalog/project-source/src/contributions/bake.kind.ts +40 -0
- package/catalog/project-source/src/contributions/builder-document.tsx +65 -0
- package/catalog/project-source/src/contributions/builder-state.ts +27 -0
- package/catalog/project-source/src/contributions/builder-ui.tsx +61 -0
- package/catalog/project-source/src/contributions/module-source.ts +502 -0
- package/catalog/project-source/src/contributions/pages.finder.ts +61 -0
- package/catalog/project-source/src/lib/audio/sfx.ts +255 -0
- package/catalog/project-source/src/lib/bake/asset-seed.ts +128 -0
- package/catalog/project-source/src/lib/bake/bake-schema.ts +65 -0
- package/catalog/project-source/src/lib/bake/clip-layering.ts +203 -0
- package/catalog/project-source/src/lib/bake/gltf-bake.ts +657 -0
- package/catalog/project-source/src/lib/bake/mesh-slice.ts +157 -0
- package/catalog/project-source/src/lib/bake/node-image.ts +463 -0
- package/catalog/project-source/src/lib/bake/object3d-bake.ts +121 -0
- package/catalog/project-source/src/lib/bake/object3d-lifecycle.ts +15 -0
- package/catalog/project-source/src/lib/bake/object3d-source.ts +8 -0
- package/catalog/project-source/src/lib/bake/params.ts +75 -0
- package/catalog/project-source/src/lib/bake/rest-relative-animation.ts +217 -0
- package/catalog/project-source/src/lib/bake/use-model.ts +95 -0
- package/catalog/project-source/src/lib/react-root/index.ts +20 -0
- package/catalog/project-source/src/lib/react-root/react-root-adapter.tsx +57 -0
- package/catalog/project-source/src/lib/reflections/ReflectionProbe.tsx +201 -0
- package/catalog/project-source/src/lib/reflections/index.ts +19 -0
- package/catalog/project-source/src/lib/reflections/probe-controller.ts +75 -0
- package/catalog/project-source/src/lib/reflections/reflection-probe-registry.ts +402 -0
- package/catalog/project-source/src/lib/reflections/volume-reflection-material.ts +416 -0
- package/catalog/project-source/src/lib/static-batch/Frozen.tsx +91 -0
- package/catalog/project-source/src/lib/static-batch/freeze.ts +579 -0
- package/catalog/project-source/src/lib/static-batch/index.ts +44 -0
- package/catalog/project-source/src/lib/static-batch/mutation-watch.ts +149 -0
- package/catalog/project-source/src/lib/static-batch/shadow-cache.ts +171 -0
- package/catalog/project-source/src/tools/bake-module.tool.ts +139 -0
- package/catalog/project-source/src/tools/bake-preview.tool.ts +475 -0
- package/dist/.vite/manifest.json +8304 -0
- package/dist/assets/AssetBudgetPanel-J9--TZPU.js +23 -0
- package/dist/assets/AudioViewer-CR7333fe.js +1 -0
- package/dist/assets/BVHLoader-6bQIXhMX.js +1 -0
- package/dist/assets/BufferGeometryUtils-Nz37EVXf.js +1 -0
- package/dist/assets/CCDIKSolver-CD7vLpsK.js +1 -0
- package/dist/assets/CSS3DRenderer-CL7ABLVK.js +1 -0
- package/dist/assets/CanvasSceneViewport-Bpi6gVTC.js +1 -0
- package/dist/assets/ColladaLoader-aVOPIsCh.js +3 -0
- package/dist/assets/ColorInput-DQBNrW-K.js +1 -0
- package/dist/assets/ConvexGeometry-CAzXlPVG.js +1 -0
- package/dist/assets/ConvexHull-BINCESFp.js +1 -0
- package/dist/assets/CopyShader-BzTUYzf6.js +24 -0
- package/dist/assets/DecalGeometry-160KRdfg.js +1 -0
- package/dist/assets/EXRLoader-Cdans_oJ.js +1 -0
- package/dist/assets/EffectComposer-DQxfsXBZ.js +1 -0
- package/dist/assets/EntityModelDocument-Ccvw66N9.js +1 -0
- package/dist/assets/EnvironmentAssetDocument-Dg-Qq6O6.js +1 -0
- package/dist/assets/FBXLoader-B4MoUtAn.js +1 -0
- package/dist/assets/Fbo-Dz-Soul3.js +1 -0
- package/dist/assets/FontLoader-CvGoVI5a.js +2 -0
- package/dist/assets/GLTFExporter-HgrCle4L.js +1 -0
- package/dist/assets/GameplaySessionTimeline-B52W3FVZ.js +1 -0
- package/dist/assets/Gltf-UHVWKz65.js +2 -0
- package/dist/assets/HDRLoader-THU88oVD.js +5 -0
- package/dist/assets/ImageViewer-Tn7s_MnW.js +1 -0
- package/dist/assets/InspectorCanvasPreview-CEr_Kxna.js +1 -0
- package/dist/assets/InspectorObjectPreview-CVejvLRg.js +1 -0
- package/dist/assets/JsonAssetDocument-C7VGcLhu.js +3 -0
- package/dist/assets/LineSegments2-CsOpKSiM.js +370 -0
- package/dist/assets/MTLLoader-_Xzd4_0p.js +2 -0
- package/dist/assets/MarchingCubes-Bsp4wrzd.js +1 -0
- package/dist/assets/MeshBVH-BiDHDcu-.js +1 -0
- package/dist/assets/ModelAssetDocument-F5GACirk.js +1 -0
- package/dist/assets/NonThreeAuthoringBootstrap-C3woOMxX.js +1 -0
- package/dist/assets/OBJLoader-BhIC6Hwo.js +5 -0
- package/dist/assets/Object3DDocumentViewport-DB7T0NTl.js +2 -0
- package/dist/assets/Object3DPreview-DL8oi7Xs.js +2 -0
- package/dist/assets/OnlineAssetDetail-C6z9RvYK.js +2 -0
- package/dist/assets/OrbitControls-CM3bPray.js +1 -0
- package/dist/assets/OrbitControls-DLpayAaK.js +1 -0
- package/dist/assets/OrthographicCamera-bQozawrh.js +1 -0
- package/dist/assets/PLYLoader-DYQqs5Ik.js +2 -0
- package/dist/assets/PerspectiveCamera-CQ2lh01o.js +1 -0
- package/dist/assets/PlayBar-BR-Vg3Jb.js +2 -0
- package/dist/assets/RGBELoader-BgBcQv8j.js +1 -0
- package/dist/assets/RectAreaLightUniformsLib-CMMoWfll.js +1 -0
- package/dist/assets/RenderPass-DPrn-Uky.js +1 -0
- package/dist/assets/ResolutionPicker-C67vs1F3.js +1 -0
- package/dist/assets/RootSelectionOverlay-BZPlxmXU.js +1 -0
- package/dist/assets/RoundedBox-BDGBg6QZ.js +1 -0
- package/dist/assets/RoundedBoxGeometry-CyUuYPfW.js +1 -0
- package/dist/assets/STLLoader-Dyv3lsxY.js +1 -0
- package/dist/assets/SelectionBox-D_75KrNW.js +1 -0
- package/dist/assets/ShaderPass-QYRpPuFJ.js +1 -0
- package/dist/assets/ShadowEditor-DkImwu1Y.js +1 -0
- package/dist/assets/SimplifyModifier-C8ayvRzd.js +1 -0
- package/dist/assets/SkeletonUtils-Cps8GOC6.js +1 -0
- package/dist/assets/Sky-CQKCgYDs.js +154 -0
- package/dist/assets/SourceAssetViewer-2SWQoUut.js +12 -0
- package/dist/assets/StageHost-BN-IoTef.js +2 -0
- package/dist/assets/StoryPreviewMount-DjyxRCXu.js +1 -0
- package/dist/assets/TDSLoader-D_T8XJ81.js +1 -0
- package/dist/assets/TextGeometry-DOWAEUV0.js +1 -0
- package/dist/assets/ThreeBoardDocument-CVncZyIq.js +2 -0
- package/dist/assets/ThreeIsolationSceneContent-C8C8whSF.js +1 -0
- package/dist/assets/ToolObject3DPreview-BRoDWe4Q.js +1 -0
- package/dist/assets/TrackballControls-DfznuQTh.js +1 -0
- package/dist/assets/TransientHint-BB1fwrvK.js +1 -0
- package/dist/assets/UnrealBloomPass-CR-cS_K5.js +83 -0
- package/dist/assets/VideoViewer-C6kocgIU.js +1 -0
- package/dist/assets/_commonjsHelpers-D6-XlEtG.js +1 -0
- package/dist/assets/active-ingest-CT6jSGhr.js +3 -0
- package/dist/assets/adapter-surface-Y37Nzcyw.js +1 -0
- package/dist/assets/after-paint-CB4bz1-e.js +1 -0
- package/dist/assets/agx-base-srgb-BjWZJgrZ.lut +0 -0
- package/dist/assets/agx-look-medium-high-contrast-D5F_ob1L.lut +0 -0
- package/dist/assets/agx-look-punchy-dxsnycNs.lut +0 -0
- package/dist/assets/animation-clock-CnjZqfJp.js +1 -0
- package/dist/assets/asset-budget-asset.menu-Di3KiQCd.js +1 -0
- package/dist/assets/asset-budget.action-DCzzcuTh.js +1 -0
- package/dist/assets/asset-budget.document-BWU8wnVW.js +1 -0
- package/dist/assets/asset-budget.menu-BGZpT_31.js +1 -0
- package/dist/assets/asset-editor-persistence-BBaONOJt.js +1 -0
- package/dist/assets/asset-loaders-BOy5Ss8k.js +2 -0
- package/dist/assets/asset-parse-error-BHMwBRU3.js +3 -0
- package/dist/assets/assets-BuSoGjBX.js +1 -0
- package/dist/assets/audio-meter-CyiToeUW.js +1 -0
- package/dist/assets/audio-unlock.service-CfUgUQVy.js +1 -0
- package/dist/assets/audio.utility-C6K6jem8.js +1 -0
- package/dist/assets/authoring-CgcOFhGS.js +1 -0
- package/dist/assets/autoplay.service-BZBGNSvt.js +1 -0
- package/dist/assets/axe-LkjAB6Lv.js +30 -0
- package/dist/assets/babylon-authoring-adapter-CL02gWDn.js +1 -0
- package/dist/assets/binding-Bmxqznjk.js +1 -0
- package/dist/assets/binding-identity-DcTpbvyg.js +1 -0
- package/dist/assets/binding-resolver-Dsh-6Vor.js +2 -0
- package/dist/assets/blender-emscripten-engine-9ZIRYNhe.js +2 -0
- package/dist/assets/blender-node-editor.document-iIDkB9sI.js +1 -0
- package/dist/assets/blender-properties-tab-C5CyPydi.js +3 -0
- package/dist/assets/blender-runtime-host-BpBwGv7r.js +4 -0
- package/dist/assets/blender-runtime-skin-DyQ9B6CR.js +7140 -0
- package/dist/assets/blender-runtime.document-lIYhhPtv.js +8 -0
- package/dist/assets/blender-timeline.document-77fx96N7.js +1 -0
- package/dist/assets/blender-uv-editor.document-fsT7B4gV.js +1 -0
- package/dist/assets/blender-version.status-o6dBm3n6.js +1 -0
- package/dist/assets/blender-wali-engine-wTOcrOMC.js +2 -0
- package/dist/assets/blender.command-BtJeWk-A.js +1 -0
- package/dist/assets/blender.keymap-GAQgeklK.js +1 -0
- package/dist/assets/blender.status-ujwGU6dU.js +1 -0
- package/dist/assets/blender.style-Dcem5f_T.js +1 -0
- package/dist/assets/board-guides-DziKSTss.js +1 -0
- package/dist/assets/body-marks-h_xDxiDE.js +1 -0
- package/dist/assets/bridge.command-o5gDNEwa.js +3 -0
- package/dist/assets/browserAll-DeWblVez.js +1 -0
- package/dist/assets/build-profiles.document-kW8iw8Xj.js +2 -0
- package/dist/assets/build-progress.status-BNRLytg2.js +1 -0
- package/dist/assets/build-session-C_sg3CEn.js +9 -0
- package/dist/assets/build.action-lYjTLb7K.js +1 -0
- package/dist/assets/build.command-DIGgUPU_.js +1 -0
- package/dist/assets/build.header-CKajx5U3.js +1 -0
- package/dist/assets/build.menu-BdU0QXny.js +1 -0
- package/dist/assets/build.service-CbBAyeu3.js +1 -0
- package/dist/assets/bus-mixer-CGfWDP3C.js +1 -0
- package/dist/assets/camera-KEsKvOL7.js +1 -0
- package/dist/assets/camera-runtime.inspector-B7unX4g1.js +1 -0
- package/dist/assets/canvas-entry-runtime-SfBxaR9M.js +2 -0
- package/dist/assets/capability-coverage-Czas4C18.js +7 -0
- package/dist/assets/chrome-trace-YZHMz60-.js +1 -0
- package/dist/assets/client-DNXj_opT.js +25 -0
- package/dist/assets/clip-planes-CnAI6DZn.js +1 -0
- package/dist/assets/collaboration-presence-DLoZ7EA3.js +1 -0
- package/dist/assets/collider-Bw0mXlsW.js +1 -0
- package/dist/assets/collider-dimensions-9xoFADXN.js +1 -0
- package/dist/assets/collision-system-DpNUJRJ3.js +1 -0
- package/dist/assets/command-results-CUQD49AB.js +1 -0
- package/dist/assets/component-board-registry-DrIa2csL.js +1 -0
- package/dist/assets/component-board.service-DCwcUssB.js +2 -0
- package/dist/assets/component-board.service-DidCyM4O.js +2 -0
- package/dist/assets/component-verbs.command-7xzfjGMZ.js +1 -0
- package/dist/assets/component-verbs.service-Bv9jId3q.js +1 -0
- package/dist/assets/config-CEZk921y.js +6 -0
- package/dist/assets/configurations-DTBMdaTL.js +1 -0
- package/dist/assets/connection.status-BifnQdOs.js +1 -0
- package/dist/assets/console-bridge-CHcb-xk5.js +1 -0
- package/dist/assets/constraint-De8f1Uiq.js +1 -0
- package/dist/assets/constraints.inspector-B9U9Yj-u.js +1 -0
- package/dist/assets/countdown-timer-CUm74dom.js +1 -0
- package/dist/assets/coverage.service-VTX5Mcsx.js +3 -0
- package/dist/assets/crash-null-boundary-v3a0uEgw.js +1 -0
- package/dist/assets/create-runtime-CVCc1BDx.js +1 -0
- package/dist/assets/curve-7Q79VgU4.js +1 -0
- package/dist/assets/data-asset-4hjS642k.js +5 -0
- package/dist/assets/data-check-core-CQEjuFyt.js +1 -0
- package/dist/assets/data-ref-CQI28C0D.js +1 -0
- package/dist/assets/debug-bridge-CCohG-d-.js +1 -0
- package/dist/assets/debug-registry-Dgvitx8M.js +1 -0
- package/dist/assets/defaults-DKiBJCPN.js +1 -0
- package/dist/assets/deferred-commands-CEMmvL47.js +1 -0
- package/dist/assets/deferred-ingest-play-DCJi_Pgu.js +2 -0
- package/dist/assets/design-time-layers-wYgOTgni.js +1 -0
- package/dist/assets/design-time-mount-registry-DUpXCg1U.js +1 -0
- package/dist/assets/design-time-mount.service-BzY21e1F.js +2 -0
- package/dist/assets/design-time-react-mount-DM9WLrF2.js +1 -0
- package/dist/assets/dev-build-gDYrq68M.js +1 -0
- package/dist/assets/directional-shadow-fit-BVkOKAv2.js +1 -0
- package/dist/assets/document-preview-three-Kbg2GCLm.js +1 -0
- package/dist/assets/dom-authoring-adapter-CY8C7f4T.js +1 -0
- package/dist/assets/draco3d-BBUTcNkz.js +1 -0
- package/dist/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/dist/assets/draco_encoder-2Tcju8Ns.wasm +0 -0
- package/dist/assets/edit-mode-audio.service-Bzetwd5B.js +1 -0
- package/dist/assets/edit-mode-authoring-B47TfLqu.js +1 -0
- package/dist/assets/edit-mode-networking.service-YCX6RRb_.js +1 -0
- package/dist/assets/editor-api-BoJIcyZG.js +1 -0
- package/dist/assets/editor-port-C_FeBdeV.js +1 -0
- package/dist/assets/editor-presentation-activity-C7SNSmTd.js +1 -0
- package/dist/assets/entry-adjudication-Cq8Yeb74.js +1 -0
- package/dist/assets/environment-capture-7himRh2L.js +1 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/events-156d8d12.esm-ErOhDXXB.js +36 -0
- package/dist/assets/extend-B-eOccUm.js +36 -0
- package/dist/assets/extends-CF3RwP-h.js +1 -0
- package/dist/assets/eyedropper-session-bjPXnfeX.js +1 -0
- package/dist/assets/fflate.module-DPuTF8aG.js +6 -0
- package/dist/assets/filmic-srgb-Ct35FIH6.lut +0 -0
- package/dist/assets/first-party-audio-system-DxnGJOER.js +1 -0
- package/dist/assets/first-party-navigation-system-CL5Bquub.js +1 -0
- package/dist/assets/fork-menu-BoVyw2Wu.js +1 -0
- package/dist/assets/frame-debugger-store-D1Mf_5up.js +1 -0
- package/dist/assets/frame-delta-D2nsw_Ht.js +1 -0
- package/dist/assets/frame-pacing-D5acoOzO.js +1 -0
- package/dist/assets/frame-selector-cache-cCeUbcjG.js +1 -0
- package/dist/assets/game-DHOZVb-7.js +1 -0
- package/dist/assets/game-contract-seams-a5YXejOu.js +1 -0
- package/dist/assets/game-css-scope-transform-Eng4wS8E.js +33 -0
- package/dist/assets/game-document.service-DbRfx2V0.js +1 -0
- package/dist/assets/game-eval.command-D-7Dxbj_.js +2 -0
- package/dist/assets/game-input-seams-CIQdetRw.js +1 -0
- package/dist/assets/game-loop-CZvXlY00.js +1 -0
- package/dist/assets/game-module-access-B2mFxv-i.js +1 -0
- package/dist/assets/game.layout-CBASrvQu.js +1 -0
- package/dist/assets/gameplay-export-BoKXm51x.js +1 -0
- package/dist/assets/gameplay-replay-e5umMlXv.js +1 -0
- package/dist/assets/gameplay-rng-trap-CTNhm-UW.js +3 -0
- package/dist/assets/gameplay.command-DxcVYX6F.js +1 -0
- package/dist/assets/generation-DhwCUHPf.css +1 -0
- package/dist/assets/generation-jobs-BQrXbGp_.js +1 -0
- package/dist/assets/generation.service-CDIgObuw.js +1 -0
- package/dist/assets/generations-DzUogZsM.js +1 -0
- package/dist/assets/generations.status-CgOUBCum.js +1 -0
- package/dist/assets/hierarchy-marks-BLPfSIym.js +1 -0
- package/dist/assets/host-pointer-DbGSCjE9.js +1 -0
- package/dist/assets/hosted-asset-materialization-CiIvWkGU.js +2 -0
- package/dist/assets/ibl-override-material-BlmCxFKq.js +6 -0
- package/dist/assets/index-96FBF1Oj.js +1 -0
- package/dist/assets/index-BAWkB0dv.js +1 -0
- package/dist/assets/index-BDnS2b6S.js +1 -0
- package/dist/assets/index-BHp07vN0.js +2 -0
- package/dist/assets/index-BMxv2MNV.js +1 -0
- package/dist/assets/index-BP6wv2A0.js +5 -0
- package/dist/assets/index-BcCBqgM_.js +952 -0
- package/dist/assets/index-Bge9JUr6.js +1 -0
- package/dist/assets/index-BjTCPEQ7.js +134 -0
- package/dist/assets/index-BncK9THk.js +1 -0
- package/dist/assets/index-CJbYy-CF.js +3 -0
- package/dist/assets/index-CVCb_ImQ.js +1 -0
- package/dist/assets/index-CZd9o7Lu.js +1 -0
- package/dist/assets/index-CanA16PU.js +2 -0
- package/dist/assets/index-CinJrH5C.js +9 -0
- package/dist/assets/index-CtzuWnCS.js +1 -0
- package/dist/assets/index-D2fNXpHa.css +1 -0
- package/dist/assets/index-DUpHH6JR.js +3120 -0
- package/dist/assets/index-DUqP1RMl.js +1 -0
- package/dist/assets/index-D_NuPfjr.js +9 -0
- package/dist/assets/index-Da9JhEvW.js +25 -0
- package/dist/assets/index-DgapqSPi.js +7 -0
- package/dist/assets/index-FUuS4qwW.js +562 -0
- package/dist/assets/index-GIL7oGEO.js +4894 -0
- package/dist/assets/index-IEjydI9d.js +1 -0
- package/dist/assets/index-OrYXPk3Z.js +487 -0
- package/dist/assets/index-V1R-e5Bd.js +1 -0
- package/dist/assets/index-bQE2NA0D.js +1 -0
- package/dist/assets/index-gHZae-lw.js +13 -0
- package/dist/assets/index-mjWQmGhM.js +1 -0
- package/dist/assets/index-nmcBqr7Y.js +1 -0
- package/dist/assets/inference-diagnostics-Dhmtfbq_.js +1 -0
- package/dist/assets/ingest-BbYMC2vQ.js +1 -0
- package/dist/assets/ingest-boot-viewport-D5rI_Kk_.js +1 -0
- package/dist/assets/ingest-siblings-Bop2kqVl.js +1 -0
- package/dist/assets/ingest-source-persistence-C_6Hiyra.js +1 -0
- package/dist/assets/ingest.service-BTDnQfM1.js +1 -0
- package/dist/assets/input-manager-DH_hGW6d.js +1 -0
- package/dist/assets/input-router-BNQvtIqw.js +1 -0
- package/dist/assets/inspect-BZfi3mmo.js +1 -0
- package/dist/assets/inspection-CObLhnrs.js +1 -0
- package/dist/assets/instance-fork-actions-D3sRzS3K.js +1 -0
- package/dist/assets/instances.command-D6UkZNO8.js +1 -0
- package/dist/assets/instruments-CKR02Y8I.js +1 -0
- package/dist/assets/json-history-resource-BbxllWfV.js +1 -0
- package/dist/assets/jsx-runtime-DPjLkJKQ.js +9 -0
- package/dist/assets/light-D-_wsKTq.js +1 -0
- package/dist/assets/light-camera-factory-CWbbe9gI.js +1 -0
- package/dist/assets/lil-gui.module.min-BF55BEwA.js +10 -0
- package/dist/assets/live-behaviors-Bxq49Maz.js +1 -0
- package/dist/assets/live-gesture-lock-DJbrjOqf.js +1 -0
- package/dist/assets/loader-CaF_I7Yt.js +1 -0
- package/dist/assets/logger-Dqis-7W1.js +1 -0
- package/dist/assets/loop-gate-report-N52EWFGD.js +1 -0
- package/dist/assets/matcap-texture-CVfalpWT.js +1 -0
- package/dist/assets/material-CpAdjDxn.js +1 -0
- package/dist/assets/material-factory-Bz6h__6g.js +1 -0
- package/dist/assets/mesh-B3WPtNKO.js +1 -0
- package/dist/assets/mesh-shadow-CZnXrw_l.js +1 -0
- package/dist/assets/mikktspace.module-DO88SLib.js +1 -0
- package/dist/assets/model-asset-sections-CFvvihfx.css +1 -0
- package/dist/assets/model-asset-sections.service-CHhFS3ZX.js +1 -0
- package/dist/assets/model.layout-BJUEQYUI.js +1 -0
- package/dist/assets/models.finder-G2jQgIRu.js +1 -0
- package/dist/assets/module-mode-DVBLT9d-.js +2 -0
- package/dist/assets/mount-canvas-ingest-root-DDuAWf_C.js +2 -0
- package/dist/assets/mount-dom-ingest-root-D_EiBgpn.js +2 -0
- package/dist/assets/mount-game-6t9cKNo4.js +1 -0
- package/dist/assets/mount-ingest-root-glv2SSWf.js +2 -0
- package/dist/assets/mount-manifest-CGVxhmAw.js +1 -0
- package/dist/assets/mount-readiness-Ds0HvvLH.js +4 -0
- package/dist/assets/mount-three-ingest-root-hrUt9VVU.js +1 -0
- package/dist/assets/mounted-authoring-Dmg5xmhP.js +1 -0
- package/dist/assets/mounted-story-viewport-source-DjP6RA9U.js +1 -0
- package/dist/assets/native-debug-module-CWicoSGD.js +1 -0
- package/dist/assets/native-entry-surface-BE8LJNdu.js +1 -0
- package/dist/assets/navigation-CIiCx-SM.js +1 -0
- package/dist/assets/navmesh-workflow-store-Cmnm57TB.js +1 -0
- package/dist/assets/navmesh.menu-CAKiH8PW.js +1 -0
- package/dist/assets/navmesh.service-jgDWlgj1.js +1 -0
- package/dist/assets/network.utility-CEu5nHf7.js +1 -0
- package/dist/assets/object-marks-DwvIEBOK.js +1 -0
- package/dist/assets/object3d-authoring-subject-CH5sI2oP.js +1 -0
- package/dist/assets/output-DK8waum9.js +195 -0
- package/dist/assets/particles-Y626p22p.js +1 -0
- package/dist/assets/particles-factory-DnLOHXT8.js +1 -0
- package/dist/assets/pasteboard-materialize-BqSS4QzV.js +20 -0
- package/dist/assets/pasteboard.action-DU3UHtJG.js +2 -0
- package/dist/assets/performance-profiler-D_Jey87L.js +1 -0
- package/dist/assets/physics-registry-B2Jkp8e6.js +1 -0
- package/dist/assets/physics-registry-CUB9cT9p.js +1 -0
- package/dist/assets/pixi-authoring-adapter-CjibysC3.js +1 -0
- package/dist/assets/pixi-live-write-target-DuWydlWb.js +1 -0
- package/dist/assets/pixi-primitive-DlWbUPN9.js +1 -0
- package/dist/assets/pixi-react-root-factory-DYsznYRQ.js +1 -0
- package/dist/assets/pixi-story-model-DNkfnWUV.js +1 -0
- package/dist/assets/pixi-transform-channels-VjdohT11.js +1 -0
- package/dist/assets/play-mode-BFU2PCkG.js +22 -0
- package/dist/assets/play.command-BpqqLqkq.js +2 -0
- package/dist/assets/playtest-BXIL1JEF.js +1 -0
- package/dist/assets/pose-guard-6Osb2cFu.js +1 -0
- package/dist/assets/presentation-BPP5pXeQ.js +1 -0
- package/dist/assets/product-DFhyARJj.js +1 -0
- package/dist/assets/profiler.action-CaXJDp3t.js +1 -0
- package/dist/assets/profiler.menu-BIMF6Bi4.js +1 -0
- package/dist/assets/profiler.utility-BExw9wBO.js +1 -0
- package/dist/assets/project-authoring-session-Bnt6icLr.js +2 -0
- package/dist/assets/project-provenance-BoP3nrle.js +1 -0
- package/dist/assets/prompt-labels-DSMdM2_B.js +1 -0
- package/dist/assets/properties-bone-constraints.inspector-ByT6p7S-.js +1 -0
- package/dist/assets/properties-bone.inspector-z4qJwKO5.js +1 -0
- package/dist/assets/properties-collection.inspector-D0_3ijWU.js +1 -0
- package/dist/assets/properties-constraints.inspector-CESzjuls.js +1 -0
- package/dist/assets/properties-data.inspector-hG8Wgyzb.js +1 -0
- package/dist/assets/properties-material.inspector-CzY0JCQ6.js +1 -0
- package/dist/assets/properties-modifiers.inspector-3dDhwf7g.js +1 -0
- package/dist/assets/properties-object.inspector-CiarfxkT.js +1 -0
- package/dist/assets/properties-output.inspector-DKJxmKSD.js +1 -0
- package/dist/assets/properties-particles.inspector-CCEdYWGm.js +1 -0
- package/dist/assets/properties-physics.inspector-B2c0KUyA.js +1 -0
- package/dist/assets/properties-render.inspector-Bcm0HLRW.js +1 -0
- package/dist/assets/properties-scene.inspector-PIM_eTb_.js +1 -0
- package/dist/assets/properties-texture.inspector-CgFlnyr8.js +1 -0
- package/dist/assets/properties-view-layer.inspector-DGjF_Aqo.js +1 -0
- package/dist/assets/properties-world.inspector-CrMvaRvB.js +1 -0
- package/dist/assets/r3f-design-session-D-4yyDO9.js +2 -0
- package/dist/assets/r3f-physics-binding-BBo8oma9.js +1 -0
- package/dist/assets/r3f-root-factory-z9llD1OU.js +1 -0
- package/dist/assets/r3f-source-authoring-adapter-w6Bc60K-.js +13 -0
- package/dist/assets/rapier-physics-adapter-CaZqWLu0.js +1 -0
- package/dist/assets/rapier-physics-bridge-CMoeVo3Y.js +1 -0
- package/dist/assets/react-18-By2b4xR9.js +1 -0
- package/dist/assets/react-inspector.service-B_neRnSK.js +1 -0
- package/dist/assets/react-root-CEz5NTck.js +1 -0
- package/dist/assets/react-story-board-BDDwiE2H.js +1 -0
- package/dist/assets/react-three-fiber.esm-CycrbDlA.js +1 -0
- package/dist/assets/react-three-rapier.esm-CEfKpvxT.js +1 -0
- package/dist/assets/react-world-authoring-adapter-DmrwM3py.js +1 -0
- package/dist/assets/realm-services-CobQsq_X.js +1 -0
- package/dist/assets/rebind-controller-Dtq19Vb3.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-Dsoa_MD-.js +1 -0
- package/dist/assets/reflection-probe-Don-M3Fj.js +1 -0
- package/dist/assets/reflection-probe-capture.inspector-CmFUoJCx.js +1 -0
- package/dist/assets/register-render-vitals-BiZk29b2.js +1 -0
- package/dist/assets/registry-02YZjMNp.js +1 -0
- package/dist/assets/relative-path-guard-GiM7Lsz8.js +1 -0
- package/dist/assets/render-audio-control-vMRlePWb.js +1 -0
- package/dist/assets/render-batch-system-0vq7p0VC.js +1 -0
- package/dist/assets/render-census-BrQX_CXv.js +1 -0
- package/dist/assets/render-control-Df3yTiyr.js +1 -0
- package/dist/assets/render-env-D2IV2x5k.js +1 -0
- package/dist/assets/render-features-CPQ_e568.js +1 -0
- package/dist/assets/render-seed-DF4JHy7P.js +1 -0
- package/dist/assets/render-settings-Bw4W9SYF.js +1 -0
- package/dist/assets/render-vitals-BZ5HtvMi.js +1 -0
- package/dist/assets/renderer-config-BqG4O8i9.js +1 -0
- package/dist/assets/resolve-three-SZE_WWUM.js +702 -0
- package/dist/assets/root-seam-contract-Z0VWX3Bk.js +1 -0
- package/dist/assets/rrweb-Cd2ifIRP.js +142 -0
- package/dist/assets/run-ticks-settled-C4ksViyA.js +1 -0
- package/dist/assets/runtime-inspection-l2iFQSsg.js +1 -0
- package/dist/assets/scene-capture-BQKzU0NT.js +1 -0
- package/dist/assets/scene-capture-CGYF-Viw.js +1 -0
- package/dist/assets/scene-document.service-qkDjUrlP.js +2 -0
- package/dist/assets/scene-framing-Cat0GtyJ.js +1 -0
- package/dist/assets/scene-index-B6BZOHMk.js +1 -0
- package/dist/assets/scene-query-C7_N-TpI.js +1 -0
- package/dist/assets/schema-kl9-2B_J.js +1 -0
- package/dist/assets/sculpt.layout-zJF55yCR.js +1 -0
- package/dist/assets/seeded-random-i3mtgBKZ.js +1 -0
- package/dist/assets/session-CZ5JPg-z.js +4877 -0
- package/dist/assets/setup-audio-S8GQF91f.js +1 -0
- package/dist/assets/setup-particles-GCXlnkj_.js +1 -0
- package/dist/assets/setup-physics-BeZ7-LKD.js +1 -0
- package/dist/assets/setup-renderer-DVXaSGin.js +1587 -0
- package/dist/assets/shading.layout-DULYZV6M.js +1 -0
- package/dist/assets/sim-clock-CLflx7kg.js +1 -0
- package/dist/assets/sky-precompute-worker-ChN7J87v.js +1 -0
- package/dist/assets/soft-particle-depth-DGvfQDi1.js +1 -0
- package/dist/assets/source-object3d-authoring-adapter-B-L6Ha6f.js +1 -0
- package/dist/assets/source-persistence-backend-C-aR-B_Y.js +1 -0
- package/dist/assets/spark-renderer-lifecycle-ynWESJcv.js +1 -0
- package/dist/assets/spark.module-4pXQoz5d.js +12120 -0
- package/dist/assets/stage-invalidation-DBg3C3Ta.js +1 -0
- package/dist/assets/stage-keyboard-n-5kKH-m.js +1 -0
- package/dist/assets/stage-overlay-set-B9zp2sLB.js +1 -0
- package/dist/assets/stage-presence-markers-DiGI6A45.js +1 -0
- package/dist/assets/standard-viewport-dressing-CzjAe_0A.js +7 -0
- package/dist/assets/state-bridge-DLP1jLRW.js +1 -0
- package/dist/assets/state-watch.action-BOvQR2qt.js +1 -0
- package/dist/assets/state-watch.menu-CNLvDeeY.js +1 -0
- package/dist/assets/state-watch.utility-Bg1tTTQU.js +1 -0
- package/dist/assets/static-batch-advisor-B13QEH7d.js +1 -0
- package/dist/assets/stats.module-BL02l9NC.js +1 -0
- package/dist/assets/story-arg-descriptors-Ek86ExX7.js +1 -0
- package/dist/assets/story-capture-_gC8dXV-.js +1 -0
- package/dist/assets/story-documents--TDLaPeL.js +3 -0
- package/dist/assets/story-documents.service-BiS_YbNQ.js +1 -0
- package/dist/assets/story-documents.service-CxOPYJ2j.js +1 -0
- package/dist/assets/story-media-presence-BHzk2IV5.js +1 -0
- package/dist/assets/story-mount-turn-DFRTj5hi.js +1 -0
- package/dist/assets/story-pixi-preview-BUrOJZXS.js +1 -0
- package/dist/assets/story-presentation-j2AVN-mK.js +1 -0
- package/dist/assets/story-three-preview-BHEDZzcl.js +2 -0
- package/dist/assets/structural-ids-Dc-7rEwi.js +1 -0
- package/dist/assets/structural-signature-0w-i_ats.js +1 -0
- package/dist/assets/surface-content-Dg9Kkazf.js +1 -0
- package/dist/assets/surface-state-DaxyruCj.js +3 -0
- package/dist/assets/system-adapters-rIC8FSGP.js +1 -0
- package/dist/assets/system-runner-DkOjxEBt.js +1 -0
- package/dist/assets/system-slot-E2mEdiih.js +1 -0
- package/dist/assets/team-playtest.service-DqOinFVA.js +1 -0
- package/dist/assets/texture.layout-DASpHVht.js +1 -0
- package/dist/assets/three-CDSueJyJ.js +1 -0
- package/dist/assets/three-authoring-adapter-CmSQ3Vui.js +1 -0
- package/dist/assets/three-authoring.service-iuHLwtf1.js +2 -0
- package/dist/assets/three-ingest-runtime-QzDYk6tU.js +1 -0
- package/dist/assets/three-projection-core-BmKMZ-nf.js +1 -0
- package/dist/assets/three-viewport-presentation-QBQpSdMh.js +1 -0
- package/dist/assets/three.module-D396a9Mn.js +3840 -0
- package/dist/assets/three.quarks.esm-B1C61jGo.js +1039 -0
- package/dist/assets/tools-B_NJsk05.js +1 -0
- package/dist/assets/transform-writer-BhBBTx33.js +1 -0
- package/dist/assets/transport.header-B-6HQlU2.js +1 -0
- package/dist/assets/trigger-volume-BNK2XzPL.js +1 -0
- package/dist/assets/tuples-CIQ4Bn1U.js +1 -0
- package/dist/assets/tween.module-DciWpAG6.js +1 -0
- package/dist/assets/ui-board-document-Bwtt--xp.js +2 -0
- package/dist/assets/unexported-game-trap-Dm1JYKHy.js +2 -0
- package/dist/assets/unmanaged-root-detector-B8CpbkSN.js +2 -0
- package/dist/assets/upstream-pin-BdgRH6ID.js +1 -0
- package/dist/assets/use-data-Dzq-MytE.js +1 -0
- package/dist/assets/use-live-actor-state-DErbWQWF.js +1 -0
- package/dist/assets/use-selection-DbR73-xd.js +1 -0
- package/dist/assets/user-data-BB2Of6Ht.js +1 -0
- package/dist/assets/utility-view-state-BHypxA33.js +1 -0
- package/dist/assets/uv-editing.layout-BEKHxmHw.js +1 -0
- package/dist/assets/vgai-shared-fontawesome-CU65D4Ij.js +1 -0
- package/dist/assets/vgai-shared-jsx-dev-runtime-DFDKL7fx.js +1 -0
- package/dist/assets/vgai-shared-jsx-runtime-DIqBpTut.js +1 -0
- package/dist/assets/vgai-shared-react-BT7rMc2H.js +1 -0
- package/dist/assets/vgai-shared-react-dom-DFajMqMT.js +1 -0
- package/dist/assets/vgai-shared-react-dom-client-S0ozfl6_.js +1 -0
- package/dist/assets/vgai-shared-three-Bc5PvpLM.js +1 -0
- package/dist/assets/viewport-pick-context-DkawXbEi.js +1 -0
- package/dist/assets/viewport-root-presentation-CWsE56K6.js +1 -0
- package/dist/assets/viewport-shading-BuA88e_A.js +1 -0
- package/dist/assets/viewport-shading-boundary-B_-_gABg.js +82 -0
- package/dist/assets/viewport-surface-status-B3om4Kcf.js +1 -0
- package/dist/assets/vision_bundle-DX0dWqEa.js +15 -0
- package/dist/assets/voxel-surface-DtcsSWRL.js +1 -0
- package/dist/assets/wav-encode-D31zWF6O.js +1 -0
- package/dist/assets/webgl-frame-capture-FlF9zQmW.js +1 -0
- package/dist/assets/webgl-gpu-timer-D3YX7RaD.js +1 -0
- package/dist/assets/webworkerAll-CM2o_dnz.js +1 -0
- package/dist/assets/worker-UrMcrc5_.js +2 -0
- package/dist/assets/world-documents-147tiFZe.js +1 -0
- package/dist/assets/world-root-stage-binding-vwTrtBz6.js +1 -0
- package/dist/assets/world-state-BWG-IcOd.js +1 -0
- package/dist/assets/writer-B-KovzkA.js +5 -0
- package/dist/assets/xstate-animation-binding-Brd0NST6.js +1 -0
- package/dist/assets/xstate-animation-meta-Q3u9zh_m.js +1 -0
- package/dist/assets/xstate-behavior.action-B8UluFBK.js +1 -0
- package/dist/assets/xstate-behavior.document-CKAsE45I.js +1 -0
- package/dist/assets/xstate-behavior.inspector-vJJyuifc.js +1 -0
- package/dist/assets/xstate-behavior.menu-BXbPICIh.js +1 -0
- package/dist/assets/xstate-inspection-BsZv8LS9.js +1 -0
- package/dist/vgai-shared-react.json +10 -0
- package/dist/vgai-shared-three.json +3 -0
- package/dist-node/cli.js +9320 -0
- package/dist-node/create.js +3239 -0
- package/package.json +91 -0
- package/presets.mjs +2 -0
- package/src/contributions.d.ts +4 -0
- package/src/index.ts +38 -0
- package/starter/cube.blend +0 -0
- package/starter/cube.py +81 -0
- package/template/.agents/references/project-manual.md +341 -0
- package/template/.agents/skills/editor/SKILL.md +53 -0
- package/template/.agents/skills/editor/agents/openai.yaml +4 -0
- package/template/.agents/skills/editor/references/editor-control.md +363 -0
- package/template/.agents/skills/vgai-3d-models/SKILL.md +64 -0
- package/template/.agents/skills/vgai-ingest-existing-game/SKILL.md +112 -0
- package/template/.agents/skills/vgai-threejs-authoring/SKILL.md +347 -0
- package/template/.agents/skills/vgai-threejs-authoring/agents/openai.yaml +4 -0
- package/template/.agents/skills/vgai-threejs-authoring/references/helper-catalog.md +65 -0
- package/template/.claude/skills/editor/SKILL.md +53 -0
- package/template/.claude/skills/editor/agents/openai.yaml +4 -0
- package/template/.claude/skills/editor/references/editor-control.md +363 -0
- package/template/.claude/skills/vgai-3d-models/SKILL.md +64 -0
- package/template/.claude/skills/vgai-ingest-existing-game/SKILL.md +112 -0
- package/template/.claude/skills/vgai-threejs-authoring/SKILL.md +347 -0
- package/template/.claude/skills/vgai-threejs-authoring/agents/openai.yaml +4 -0
- package/template/.claude/skills/vgai-threejs-authoring/references/helper-catalog.md +65 -0
- package/template/.github/copilot-instructions.md +6 -0
- package/template/.github/skills/editor/SKILL.md +53 -0
- package/template/.github/skills/editor/agents/openai.yaml +4 -0
- package/template/.github/skills/editor/references/editor-control.md +363 -0
- package/template/.github/skills/vgai-3d-models/SKILL.md +64 -0
- package/template/.github/skills/vgai-ingest-existing-game/SKILL.md +112 -0
- package/template/.github/skills/vgai-threejs-authoring/SKILL.md +347 -0
- package/template/.github/skills/vgai-threejs-authoring/agents/openai.yaml +4 -0
- package/template/.github/skills/vgai-threejs-authoring/references/helper-catalog.md +65 -0
- package/template/.mcp.json +12 -0
- package/template/.vgai/assets.json +1 -0
- package/template/.vgai/provenance.json +4 -0
- package/template/.volter/tracker/validation/.preset.base.mts +178 -0
- package/template/.volter/tracker/validation/preset.mts +186 -0
- package/template/.volter/tracker/visualizer/.extension.base.tsx +14 -0
- package/template/.volter/tracker/visualizer/extension.tsx +14 -0
- package/template/.volter/tracker-config.json +17 -0
- package/template/AGENTS.md +176 -0
- package/template/CLAUDE.md +1 -0
- package/template/DEVLOG.md +20 -0
- package/template/IDIOMS.md +118 -0
- package/template/LICENSE +30 -0
- package/template/README.md +83 -0
- package/template/ROADMAP.md +20 -0
- package/template/asset-manifest.json +3 -0
- package/template/check-idioms.ts +1533 -0
- package/template/index.html +18 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +62 -0
- package/template/public/jsm/libs/basis/basis_transcoder.js +19 -0
- package/template/public/jsm/libs/basis/basis_transcoder.wasm +0 -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/shaders/daylight-sky.frag.glsl +15 -0
- package/template/public/shaders/daylight-sky.vert.glsl +7 -0
- package/template/scripts/patch-drei-stats-gl.mjs +35 -0
- package/template/server/colyseus-setup.ts +111 -0
- package/template/server/loopback.ts +76 -0
- package/template/server/main.ts +78 -0
- package/template/server/rooms/arena-room.ts +488 -0
- package/template/server/rooms/game-room.ts +274 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/bot/QaTester.tsx +35 -0
- package/template/src/bot/behaviors.ts +203 -0
- package/template/src/bot/bot-station.ts +192 -0
- package/template/src/bot/tester-run.ts +238 -0
- package/template/src/bot/tester-station.ts +193 -0
- package/template/src/components/environment/DaylightSky.tsx +54 -0
- package/template/src/components/environment/Ground.tsx +129 -0
- package/template/src/components/environment/SceneAtmosphere.tsx +14 -0
- package/template/src/components/environment/SoftGroundShadow.tsx +62 -0
- package/template/src/components/environment/SunShadowFit.tsx +141 -0
- package/template/src/contributions/README.md +98 -0
- package/template/src/contributions/analytics.analytics.tsx +26 -0
- package/template/src/contributions/data.document.tsx +35 -0
- package/template/src/contributions/pages.finder.ts +61 -0
- package/template/src/contributions/tester.inspector.tsx +64 -0
- package/template/src/contributions/use-game-modules.ts +80 -0
- package/template/src/data/README.md +17 -0
- package/template/src/input.ts +97 -0
- package/template/src/lib/audio/capability.json +4 -0
- package/template/src/lib/audio/sfx.ts +255 -0
- package/template/src/lib/react-root/capability.json +4 -0
- package/template/src/lib/react-root/index.ts +20 -0
- package/template/src/lib/react-root/react-root-adapter.tsx +57 -0
- package/template/src/lib/reflections/ReflectionProbe.tsx +201 -0
- package/template/src/lib/reflections/capability.json +4 -0
- package/template/src/lib/reflections/index.ts +19 -0
- package/template/src/lib/reflections/probe-controller.ts +75 -0
- package/template/src/lib/reflections/reflection-probe-registry.ts +402 -0
- package/template/src/lib/reflections/volume-reflection-material.ts +416 -0
- package/template/src/lib/static-batch/Frozen.tsx +91 -0
- package/template/src/lib/static-batch/capability.json +4 -0
- package/template/src/lib/static-batch/freeze.ts +579 -0
- package/template/src/lib/static-batch/index.ts +44 -0
- package/template/src/lib/static-batch/mutation-watch.ts +149 -0
- package/template/src/lib/static-batch/shadow-cache.ts +171 -0
- package/template/src/main.ts +63 -0
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/prefabs/HeroBox.stories.tsx +23 -0
- package/template/src/prefabs/HeroBox.tsx +35 -0
- package/template/src/scenes/MainScene.tsx +77 -0
- package/template/src/world.tsx +87 -0
- package/template/tsconfig.json +35 -0
- package/template/tsconfig.node.json +32 -0
- package/template/tsconfig.tools.json +22 -0
- package/template/validate-asset-content.ts +27 -0
- package/template/validate-assets.ts +60 -0
- package/template/validate-manifest.ts +55 -0
- package/template/vgai.adapter.ts +76 -0
- package/template/vgai.project.json +24 -0
- package/template/vite.config.ts +247 -0
- package/template-dotfiles/gitignore +63 -0
- package/workbench/src/media/game-cover.css +83 -0
- package/workbench/src/product.contribution.ts +188 -0
- package/workbench/src/vgaiGameSkew.ts +276 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
|
|
3
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
4
|
+
import { createImageBitmapFromBlob, NodeImageData, NodeOffscreenCanvas } from '../bake/node-image';
|
|
5
|
+
import { disposeObject3D } from '../bake/object3d-lifecycle';
|
|
6
|
+
|
|
7
|
+
const polyfillKeys = [
|
|
8
|
+
'self',
|
|
9
|
+
'ProgressEvent',
|
|
10
|
+
'createImageBitmap',
|
|
11
|
+
'FileReader',
|
|
12
|
+
'ImageData',
|
|
13
|
+
'OffscreenCanvas',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
function arrayBufferToBase64(value: ArrayBuffer): string {
|
|
17
|
+
const bytes = new Uint8Array(value);
|
|
18
|
+
let binary = '';
|
|
19
|
+
for (let offset = 0; offset < bytes.length; offset += 0x8000) {
|
|
20
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 0x8000));
|
|
21
|
+
}
|
|
22
|
+
return btoa(binary);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The browser globals three's GLTF exporter and loader reach for, supplied
|
|
27
|
+
* for Node. Returns the restore function; never overwrites a global that
|
|
28
|
+
* already exists, so a real browser (or a jsdom-flavoured test) keeps its
|
|
29
|
+
* own.
|
|
30
|
+
*
|
|
31
|
+
* `ImageData` + `OffscreenCanvas` + a real `createImageBitmap` are the
|
|
32
|
+
* TEXTURE half, and they are what make a material MAP survive the bake:
|
|
33
|
+
* `GLTFExporter.processImage` draws a `DataTexture` into
|
|
34
|
+
* `getCanvas()` — which prefers `OffscreenCanvas` precisely when
|
|
35
|
+
* `document` is undefined — via `new ImageData(...)`, then asks
|
|
36
|
+
* `convertToBlob` for the PNG bytes it embeds; `GLTFLoader` reverses that
|
|
37
|
+
* through `ImageBitmapLoader`, whose `createImageBitmap` used to be
|
|
38
|
+
* stubbed here to an empty object, so every re-imported texture came back
|
|
39
|
+
* with no pixels behind it. All three live in `node-image.ts`.
|
|
40
|
+
*/
|
|
41
|
+
export function installNodeThreePolyfills(): () => void {
|
|
42
|
+
const target = globalThis as unknown as Record<string, unknown>;
|
|
43
|
+
const previous = new Map(
|
|
44
|
+
polyfillKeys.map((key) => [key, Object.getOwnPropertyDescriptor(globalThis, key)]),
|
|
45
|
+
);
|
|
46
|
+
if (!target['self']) target['self'] = globalThis;
|
|
47
|
+
if (!target['ImageData']) target['ImageData'] = NodeImageData;
|
|
48
|
+
if (!target['OffscreenCanvas']) target['OffscreenCanvas'] = NodeOffscreenCanvas;
|
|
49
|
+
if (!target['ProgressEvent']) {
|
|
50
|
+
target['ProgressEvent'] = class {
|
|
51
|
+
readonly type: string;
|
|
52
|
+
constructor(type: string, init: Record<string, unknown> = {}) {
|
|
53
|
+
this.type = type;
|
|
54
|
+
Object.assign(this, init);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!target['createImageBitmap']) target['createImageBitmap'] = createImageBitmapFromBlob;
|
|
59
|
+
if (!target['FileReader']) {
|
|
60
|
+
target['FileReader'] = class {
|
|
61
|
+
result: string | ArrayBuffer | null = null;
|
|
62
|
+
onloadend: (() => void) | null = null;
|
|
63
|
+
readAsArrayBuffer(blob: Blob): void {
|
|
64
|
+
void blob.arrayBuffer().then((value) => {
|
|
65
|
+
this.result = value;
|
|
66
|
+
this.onloadend?.();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
readAsDataURL(blob: Blob): void {
|
|
70
|
+
void blob.arrayBuffer().then((value) => {
|
|
71
|
+
this.result = `data:${blob.type || 'application/octet-stream'};base64,${arrayBufferToBase64(value)}`;
|
|
72
|
+
this.onloadend?.();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return () => {
|
|
78
|
+
for (const key of polyfillKeys) {
|
|
79
|
+
const descriptor = previous.get(key);
|
|
80
|
+
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
|
|
81
|
+
else Reflect.deleteProperty(globalThis, key);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Editor-only chrome a builder attached to its graph, flagged
|
|
88
|
+
* `userData.isRigHelper` (e.g. a `SkeletonHelper`). It is detached for the
|
|
89
|
+
* duration of the export and put back afterwards.
|
|
90
|
+
*
|
|
91
|
+
* Why it must go: the export runs with `onlyVisible: false` (a builder may
|
|
92
|
+
* legitimately bake hidden geometry), so an invisible helper would otherwise
|
|
93
|
+
* ship inside the game asset — and a `SkeletonHelper`'s vertex buffer is a
|
|
94
|
+
* SNAPSHOT of world bone positions, so it also skews the bounds validation
|
|
95
|
+
* below the moment a param moves the rig after the helper was built. Baked
|
|
96
|
+
* output is the model; presentation chrome stays outside the baked root
|
|
97
|
+
* (the `vgai-3d-assets` skill, "Parametric asset libs").
|
|
98
|
+
*/
|
|
99
|
+
function detachRigHelpers(root: THREE.Object3D): () => void {
|
|
100
|
+
const detached: Array<{ object: THREE.Object3D; parent: THREE.Object3D }> = [];
|
|
101
|
+
root.traverse((object) => {
|
|
102
|
+
if (object.userData['isRigHelper'] === true && object.parent) {
|
|
103
|
+
detached.push({ object, parent: object.parent });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
for (const { object, parent } of detached) parent.remove(object);
|
|
107
|
+
return () => {
|
|
108
|
+
for (const { object, parent } of detached) parent.add(object);
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The morph targets one mesh of a source graph carries, in export order. */
|
|
113
|
+
export interface MorphTargetSet {
|
|
114
|
+
mesh: string;
|
|
115
|
+
names: readonly string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The morph targets a source graph will actually export — and the assertion
|
|
120
|
+
* that it will.
|
|
121
|
+
*
|
|
122
|
+
* `GLTFExporter` reads morph targets off the MESH (`morphTargetInfluences`
|
|
123
|
+
* for how many, `morphTargetDictionary` for their names), not off the
|
|
124
|
+
* geometry, and it exports NOTHING when the mesh half is missing — the
|
|
125
|
+
* classic silent loss, since `Mesh.updateMorphTargets()` only runs in the
|
|
126
|
+
* constructor and in `GLTFLoader`. So a mesh whose geometry carries morph
|
|
127
|
+
* attributes must carry the matching named influences, or this throws.
|
|
128
|
+
*/
|
|
129
|
+
export function collectMorphTargets(root: THREE.Object3D): readonly MorphTargetSet[] {
|
|
130
|
+
const sets: MorphTargetSet[] = [];
|
|
131
|
+
root.traverse((object) => {
|
|
132
|
+
const mesh = object as THREE.Mesh;
|
|
133
|
+
if (!mesh.isMesh) return;
|
|
134
|
+
const attributes = mesh.geometry.morphAttributes['position'] ?? [];
|
|
135
|
+
if (attributes.length === 0) return;
|
|
136
|
+
const influences = mesh.morphTargetInfluences ?? [];
|
|
137
|
+
if (influences.length !== attributes.length) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Mesh '${mesh.name}' geometry carries ${attributes.length} morph targets but the mesh ` +
|
|
140
|
+
`carries ${influences.length} influences, so the export would drop them. Call ` +
|
|
141
|
+
'updateMorphTargets() after authoring morph attributes (the Mesh constructor does it ' +
|
|
142
|
+
'for geometry that already carries them).',
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const dictionary = mesh.morphTargetDictionary ?? {};
|
|
146
|
+
const names = attributes.map((attribute, index) => {
|
|
147
|
+
if (!attribute.name || dictionary[attribute.name] !== index) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Mesh '${mesh.name}' morph target ${index} is unnamed or missing from its ` +
|
|
150
|
+
'morphTargetDictionary, so the baked GLB would carry no target names.',
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return attribute.name;
|
|
154
|
+
});
|
|
155
|
+
sets.push({ mesh: mesh.name, names });
|
|
156
|
+
});
|
|
157
|
+
return sets;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function exportObject3DToGlb(
|
|
161
|
+
root: THREE.Object3D,
|
|
162
|
+
animations: readonly THREE.AnimationClip[],
|
|
163
|
+
): Promise<Uint8Array> {
|
|
164
|
+
collectMorphTargets(root);
|
|
165
|
+
const reattach = detachRigHelpers(root);
|
|
166
|
+
try {
|
|
167
|
+
const result = await new Promise<ArrayBuffer>((resolve, reject) => {
|
|
168
|
+
new GLTFExporter().parse(
|
|
169
|
+
root,
|
|
170
|
+
(value) => {
|
|
171
|
+
if (value instanceof ArrayBuffer) resolve(value);
|
|
172
|
+
else reject(new Error('GLTFExporter returned JSON while binary output was requested.'));
|
|
173
|
+
},
|
|
174
|
+
reject,
|
|
175
|
+
{ binary: true, animations: [...animations], onlyVisible: false },
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
return new Uint8Array(result);
|
|
179
|
+
} finally {
|
|
180
|
+
reattach();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface GlbValidationOptions {
|
|
185
|
+
requiredClips: readonly string[];
|
|
186
|
+
requireSkinned?: boolean;
|
|
187
|
+
forbidSkinned?: boolean;
|
|
188
|
+
minimumAnimatedNodes?: number;
|
|
189
|
+
expectedForward?: readonly [number, number, number];
|
|
190
|
+
minimumHeight?: number;
|
|
191
|
+
maximumHeight?: number;
|
|
192
|
+
groundYRange?: readonly [number, number];
|
|
193
|
+
/** Exact bone count the reimported scene must contain — proves the whole
|
|
194
|
+
* skeleton survived the export/reimport round trip. */
|
|
195
|
+
expectedBoneCount?: number;
|
|
196
|
+
/** Prove vertex colors survive the round trip: the named reimported mesh
|
|
197
|
+
* must carry a `color` attribute whose value at `index` matches the
|
|
198
|
+
* source-sampled `value` (RGB, within `tolerance`, default 2e-3). */
|
|
199
|
+
vertexColorSample?: {
|
|
200
|
+
mesh: string;
|
|
201
|
+
index: number;
|
|
202
|
+
value: readonly [number, number, number];
|
|
203
|
+
tolerance?: number;
|
|
204
|
+
};
|
|
205
|
+
/** Every morph target the SOURCE authored, per mesh — each name must come
|
|
206
|
+
* back off the reimported GLB with data behind it. Derived from the source
|
|
207
|
+
* graph by `bakeObject3DSource`, exactly as `requiredClips` is. */
|
|
208
|
+
requiredMorphTargets?: readonly MorphTargetSet[];
|
|
209
|
+
/** UV sets the reimported GLB must still carry, in the mesh kit's channel
|
|
210
|
+
* vocabulary: `'uv'` is glTF `TEXCOORD_0`, `'uv2'` is `TEXCOORD_1` (which
|
|
211
|
+
* three loads back as its `uv1` attribute — it renamed the second set in
|
|
212
|
+
* r152). Presence somewhere in the scene, exactly like `requiredClips`;
|
|
213
|
+
* which meshes carry UVs is the source's business. Without this a
|
|
214
|
+
* dropped TEXCOORD is invisible until a texture renders untextured. */
|
|
215
|
+
requiredUvChannels?: readonly ('uv' | 'uv2')[];
|
|
216
|
+
/** Prove a material's TEXTURE MAP survives the round trip, the way
|
|
217
|
+
* `vertexColorSample` proves vertex colors do: find the material by NAME
|
|
218
|
+
* (`slotMaterialMesh` names each material after its slot), read the named
|
|
219
|
+
* map's decoded image, and compare the pixel at `pixel` against `value`
|
|
220
|
+
* (RGB 0–1, within `tolerance`, default one 8-bit step). A checker
|
|
221
|
+
* texture makes this a real correspondence check — sample a cell you know
|
|
222
|
+
* the color of. */
|
|
223
|
+
materialMapSample?: {
|
|
224
|
+
material: string;
|
|
225
|
+
/** Which map (default `'map'`). */
|
|
226
|
+
map?: 'map' | 'emissiveMap' | 'metalnessMap' | 'normalMap' | 'roughnessMap' | 'aoMap';
|
|
227
|
+
/** `[x, y]` in image pixels from the top-left. */
|
|
228
|
+
pixel: readonly [number, number];
|
|
229
|
+
value: readonly [number, number, number];
|
|
230
|
+
tolerance?: number;
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface GlbValidationResult {
|
|
235
|
+
meshCount: number;
|
|
236
|
+
skinnedMeshCount: number;
|
|
237
|
+
boneCount: number;
|
|
238
|
+
animatedNodeCount: number;
|
|
239
|
+
clips: string[];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function assertFiniteValues(label: string, values: Iterable<number>): void {
|
|
243
|
+
let index = 0;
|
|
244
|
+
for (const value of values) {
|
|
245
|
+
if (!Number.isFinite(value))
|
|
246
|
+
throw new Error(`${label} contains a non-finite value at ${index}.`);
|
|
247
|
+
index += 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: exhaustive attribute/index validation is clearest as one bounded graph check.
|
|
252
|
+
function validateGeometry(object: THREE.Mesh): void {
|
|
253
|
+
const geometry = object.geometry;
|
|
254
|
+
for (const [name, attribute] of Object.entries(geometry.attributes)) {
|
|
255
|
+
for (let vertex = 0; vertex < attribute.count; vertex += 1) {
|
|
256
|
+
for (let component = 0; component < attribute.itemSize; component += 1) {
|
|
257
|
+
const value = attribute.getComponent(vertex, component);
|
|
258
|
+
if (!Number.isFinite(value)) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Geometry '${object.name}' attribute '${name}' contains a non-finite value at ` +
|
|
261
|
+
`vertex ${vertex}, component ${component}.`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (geometry.index) {
|
|
268
|
+
const positionCount = geometry.getAttribute('position')?.count ?? 0;
|
|
269
|
+
for (let index = 0; index < geometry.index.count; index += 1) {
|
|
270
|
+
const value = geometry.index.getX(index);
|
|
271
|
+
if (!Number.isInteger(value) || value < 0 || value >= positionCount) {
|
|
272
|
+
throw new Error(`Geometry '${object.name}' contains invalid index ${value} at ${index}.`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: joint, inverse-bind, index, and weight invariants form one skin validity proof.
|
|
279
|
+
function validateSkin(mesh: THREE.SkinnedMesh): void {
|
|
280
|
+
const { skeleton, geometry } = mesh;
|
|
281
|
+
const skinIndex = geometry.getAttribute('skinIndex');
|
|
282
|
+
const skinWeight = geometry.getAttribute('skinWeight');
|
|
283
|
+
if (!skinIndex || !skinWeight || skinIndex.itemSize < 4 || skinWeight.itemSize < 4) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`SkinnedMesh '${mesh.name}' is missing four-component skin indices or weights.`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (
|
|
289
|
+
skinIndex.count !== skinWeight.count ||
|
|
290
|
+
skinIndex.count !== geometry.getAttribute('position')?.count
|
|
291
|
+
) {
|
|
292
|
+
throw new Error(`SkinnedMesh '${mesh.name}' has mismatched vertex and skin attribute counts.`);
|
|
293
|
+
}
|
|
294
|
+
if (skeleton.bones.length === 0 || skeleton.boneInverses.length !== skeleton.bones.length) {
|
|
295
|
+
throw new Error(`SkinnedMesh '${mesh.name}' has an incomplete skeleton or inverse bind set.`);
|
|
296
|
+
}
|
|
297
|
+
const uniqueBones = new Set(skeleton.bones);
|
|
298
|
+
if (uniqueBones.size !== skeleton.bones.length || skeleton.bones.some((bone) => !bone?.isBone)) {
|
|
299
|
+
throw new Error(`SkinnedMesh '${mesh.name}' contains missing or duplicate joints.`);
|
|
300
|
+
}
|
|
301
|
+
for (const [index, inverse] of skeleton.boneInverses.entries()) {
|
|
302
|
+
assertFiniteValues(`SkinnedMesh '${mesh.name}' inverse bind ${index}`, inverse.elements);
|
|
303
|
+
}
|
|
304
|
+
for (let vertex = 0; vertex < skinIndex.count; vertex += 1) {
|
|
305
|
+
let weightSum = 0;
|
|
306
|
+
for (let component = 0; component < 4; component += 1) {
|
|
307
|
+
const joint = skinIndex.getComponent(vertex, component);
|
|
308
|
+
const weight = skinWeight.getComponent(vertex, component);
|
|
309
|
+
if (!Number.isInteger(joint) || joint < 0 || joint >= skeleton.bones.length) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`SkinnedMesh '${mesh.name}' vertex ${vertex} references invalid joint ${joint}.`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (!Number.isFinite(weight) || weight < 0) {
|
|
315
|
+
throw new Error(
|
|
316
|
+
`SkinnedMesh '${mesh.name}' vertex ${vertex} contains invalid skin weight ${weight}.`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
weightSum += weight;
|
|
320
|
+
}
|
|
321
|
+
if (Math.abs(weightSum - 1) > 1e-3) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`SkinnedMesh '${mesh.name}' vertex ${vertex} skin weights sum to ${weightSum}.`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: time, value, quaternion, and continuity checks intentionally share clip context.
|
|
330
|
+
function validateClip(clip: THREE.AnimationClip): void {
|
|
331
|
+
if (!Number.isFinite(clip.duration) || clip.duration <= 0) {
|
|
332
|
+
throw new Error(`Animation clip '${clip.name}' has invalid duration ${clip.duration}.`);
|
|
333
|
+
}
|
|
334
|
+
if (clip.tracks.length === 0) throw new Error(`Animation clip '${clip.name}' has no tracks.`);
|
|
335
|
+
for (const track of clip.tracks) {
|
|
336
|
+
assertFiniteValues(`Animation track '${track.name}' times`, track.times);
|
|
337
|
+
assertFiniteValues(`Animation track '${track.name}' values`, track.values);
|
|
338
|
+
for (let index = 1; index < track.times.length; index += 1) {
|
|
339
|
+
if (track.times[index]! < track.times[index - 1]!) {
|
|
340
|
+
throw new Error(`Animation track '${track.name}' has decreasing key times.`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (!(track instanceof THREE.QuaternionKeyframeTrack)) continue;
|
|
344
|
+
let previous: THREE.Quaternion | undefined;
|
|
345
|
+
for (let offset = 0; offset < track.values.length; offset += 4) {
|
|
346
|
+
const value = new THREE.Quaternion(
|
|
347
|
+
track.values[offset],
|
|
348
|
+
track.values[offset + 1],
|
|
349
|
+
track.values[offset + 2],
|
|
350
|
+
track.values[offset + 3],
|
|
351
|
+
);
|
|
352
|
+
const length = value.length();
|
|
353
|
+
if (!Number.isFinite(length) || Math.abs(length - 1) > 1e-3) {
|
|
354
|
+
throw new Error(`Animation track '${track.name}' contains a non-normalized quaternion.`);
|
|
355
|
+
}
|
|
356
|
+
if (previous && previous.dot(value) < -1e-5) {
|
|
357
|
+
throw new Error(`Animation track '${track.name}' changes quaternion hemisphere.`);
|
|
358
|
+
}
|
|
359
|
+
previous = value;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Assert the round-tripped scene still carries the source's vertex colors:
|
|
365
|
+
* find the named mesh, require a `color` attribute, and compare the sampled
|
|
366
|
+
* RGB against the source value. Garmented characters are vertex-colored, so
|
|
367
|
+
* a silent color drop would ship a monochrome body. */
|
|
368
|
+
function validateVertexColorSample(
|
|
369
|
+
scene: THREE.Object3D,
|
|
370
|
+
sample: NonNullable<GlbValidationOptions['vertexColorSample']>,
|
|
371
|
+
): void {
|
|
372
|
+
let mesh: THREE.Mesh | undefined;
|
|
373
|
+
scene.traverse((object) => {
|
|
374
|
+
if (!mesh && (object as THREE.Mesh).isMesh && object.name === sample.mesh) {
|
|
375
|
+
mesh = object as THREE.Mesh;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
if (!mesh) throw new Error(`Baked GLB round-trip lost mesh '${sample.mesh}'.`);
|
|
379
|
+
const color = mesh.geometry.getAttribute('color');
|
|
380
|
+
if (!color || color.itemSize < 3) {
|
|
381
|
+
throw new Error(`Baked GLB mesh '${sample.mesh}' lost its vertex colors on round-trip.`);
|
|
382
|
+
}
|
|
383
|
+
if (sample.index < 0 || sample.index >= color.count) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
`Vertex color sample index ${sample.index} is out of range for mesh '${sample.mesh}' ` +
|
|
386
|
+
`(${color.count} vertices).`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const tolerance = sample.tolerance ?? 2e-3;
|
|
390
|
+
const actual = [0, 1, 2].map((component) => color.getComponent(sample.index, component));
|
|
391
|
+
for (const [component, expected] of sample.value.entries()) {
|
|
392
|
+
if (Math.abs(actual[component]! - expected) > tolerance) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
`Baked GLB mesh '${sample.mesh}' vertex color at ${sample.index} drifted on ` +
|
|
395
|
+
`round-trip: got [${actual.join(', ')}], expected [${sample.value.join(', ')}].`,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Channel vocabulary → three's attribute name → the glTF accessor it came
|
|
402
|
+
* from. Three renamed the second UV set to `uv1` in r152; the channel names
|
|
403
|
+
* stay the mesh kit's `uv`/`uv2`. */
|
|
404
|
+
const UV_CHANNELS = {
|
|
405
|
+
uv: { attribute: 'uv', accessor: 'TEXCOORD_0' },
|
|
406
|
+
uv2: { attribute: 'uv1', accessor: 'TEXCOORD_1' },
|
|
407
|
+
} as const;
|
|
408
|
+
|
|
409
|
+
/** Assert the reimported scene still carries each required UV set. A
|
|
410
|
+
* dropped TEXCOORD does not fail any other check here — the geometry is
|
|
411
|
+
* perfectly valid without it — and only shows up as an untextured render. */
|
|
412
|
+
function validateUvChannels(scene: THREE.Object3D, required: readonly ('uv' | 'uv2')[]): void {
|
|
413
|
+
const present = new Set<string>();
|
|
414
|
+
let meshCount = 0;
|
|
415
|
+
scene.traverse((object) => {
|
|
416
|
+
const mesh = object as THREE.Mesh;
|
|
417
|
+
if (!mesh.isMesh) return;
|
|
418
|
+
meshCount += 1;
|
|
419
|
+
for (const name of Object.keys(mesh.geometry.attributes)) present.add(name);
|
|
420
|
+
});
|
|
421
|
+
for (const channel of required) {
|
|
422
|
+
const { attribute, accessor } = UV_CHANNELS[channel];
|
|
423
|
+
if (!present.has(attribute)) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`Baked GLB round-trip carries no '${attribute}' attribute (channel '${channel}', glTF ` +
|
|
426
|
+
`${accessor}) on any of its ${meshCount} meshes — the UVs were dropped on export.`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Assert a named material's texture map survived the round trip with the
|
|
433
|
+
* right pixels behind it. Keyed by MATERIAL name rather than mesh name
|
|
434
|
+
* because a slot-grouped mesh exports as one glTF primitive per slot and
|
|
435
|
+
* comes back split into several meshes, while the material names — which
|
|
436
|
+
* ARE the slot names — survive intact. */
|
|
437
|
+
function validateMaterialMapSample(
|
|
438
|
+
scene: THREE.Object3D,
|
|
439
|
+
sample: NonNullable<GlbValidationOptions['materialMapSample']>,
|
|
440
|
+
): void {
|
|
441
|
+
const key = sample.map ?? 'map';
|
|
442
|
+
const names = new Set<string>();
|
|
443
|
+
let material: THREE.Material | undefined;
|
|
444
|
+
scene.traverse((object) => {
|
|
445
|
+
const mesh = object as THREE.Mesh;
|
|
446
|
+
if (!mesh.isMesh) return;
|
|
447
|
+
for (const candidate of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
|
|
448
|
+
if (!candidate) continue;
|
|
449
|
+
names.add(candidate.name);
|
|
450
|
+
if (!material && candidate.name === sample.material) material = candidate;
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
if (!material) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Baked GLB round-trip lost material '${sample.material}' (kept: ` +
|
|
456
|
+
`${[...names].join(', ') || 'none'}).`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
const texture = (material as unknown as Record<string, THREE.Texture | null>)[key];
|
|
460
|
+
if (!texture) {
|
|
461
|
+
throw new Error(`Baked GLB material '${sample.material}' came back with no '${key}'.`);
|
|
462
|
+
}
|
|
463
|
+
const image = texture.image as { width?: number; height?: number; data?: ArrayLike<number> };
|
|
464
|
+
if (!image?.data || !image.width || !image.height) {
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Baked GLB material '${sample.material}' '${key}' came back with no readable pixels — the ` +
|
|
467
|
+
'image was embedded but not decoded (installNodeThreePolyfills supplies the decoder).',
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
const [x, y] = sample.pixel;
|
|
471
|
+
if (x < 0 || y < 0 || x >= image.width || y >= image.height) {
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Material map sample pixel [${x}, ${y}] is outside '${sample.material}' '${key}' ` +
|
|
474
|
+
`(${image.width}×${image.height}).`,
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
const tolerance = sample.tolerance ?? 4e-3;
|
|
478
|
+
const offset = (y * image.width + x) * 4;
|
|
479
|
+
const actual = [0, 1, 2].map((component) => (image.data?.[offset + component] ?? 0) / 255);
|
|
480
|
+
for (const [component, expected] of sample.value.entries()) {
|
|
481
|
+
if (Math.abs((actual[component] as number) - expected) > tolerance) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Baked GLB material '${sample.material}' '${key}' pixel [${x}, ${y}] drifted on ` +
|
|
484
|
+
`round-trip: got [${actual.join(', ')}], expected [${sample.value.join(', ')}].`,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Assert every authored morph target survived the round trip, by NAME and
|
|
491
|
+
* with data behind it. A morph target is invisible in a render of the rest
|
|
492
|
+
* pose — nothing but this notices when a merge, an exporter quirk, or a
|
|
493
|
+
* missing `updateMorphTargets()` drops the face's blink on the way out. */
|
|
494
|
+
function validateMorphTargets(scene: THREE.Object3D, required: readonly MorphTargetSet[]): void {
|
|
495
|
+
const byName = new Map<string, THREE.Mesh>();
|
|
496
|
+
scene.traverse((object) => {
|
|
497
|
+
const mesh = object as THREE.Mesh;
|
|
498
|
+
if (mesh.isMesh && !byName.has(mesh.name)) byName.set(mesh.name, mesh);
|
|
499
|
+
});
|
|
500
|
+
for (const set of required) {
|
|
501
|
+
const mesh = byName.get(set.mesh);
|
|
502
|
+
if (!mesh) throw new Error(`Baked GLB round-trip lost mesh '${set.mesh}'.`);
|
|
503
|
+
const dictionary = mesh.morphTargetDictionary ?? {};
|
|
504
|
+
const attributes = mesh.geometry.morphAttributes['position'] ?? [];
|
|
505
|
+
const missing = set.names.filter((name) => dictionary[name] === undefined);
|
|
506
|
+
if (missing.length > 0) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
`Baked GLB mesh '${set.mesh}' lost morph targets on round-trip: ${missing.join(', ')} ` +
|
|
509
|
+
`(kept: ${Object.keys(dictionary).join(', ') || 'none'}).`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
for (const name of set.names) {
|
|
513
|
+
const attribute = attributes[dictionary[name]!];
|
|
514
|
+
if (!attribute || attribute.count === 0) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
`Baked GLB mesh '${set.mesh}' morph target '${name}' came back with no vertex data.`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function validateBoundsAndForward(scene: THREE.Object3D, options: GlbValidationOptions): void {
|
|
524
|
+
scene.updateMatrixWorld(true);
|
|
525
|
+
const bounds = new THREE.Box3().setFromObject(scene, true);
|
|
526
|
+
if (bounds.isEmpty()) throw new Error('Baked GLB round-trip contained no renderable bounds.');
|
|
527
|
+
assertFiniteValues('Baked GLB bounds', [
|
|
528
|
+
bounds.min.x,
|
|
529
|
+
bounds.min.y,
|
|
530
|
+
bounds.min.z,
|
|
531
|
+
bounds.max.x,
|
|
532
|
+
bounds.max.y,
|
|
533
|
+
bounds.max.z,
|
|
534
|
+
]);
|
|
535
|
+
const height = bounds.max.y - bounds.min.y;
|
|
536
|
+
if (options.minimumHeight !== undefined && height < options.minimumHeight) {
|
|
537
|
+
throw new Error(`Baked GLB height ${height} is below ${options.minimumHeight}.`);
|
|
538
|
+
}
|
|
539
|
+
if (options.maximumHeight !== undefined && height > options.maximumHeight) {
|
|
540
|
+
throw new Error(`Baked GLB height ${height} exceeds ${options.maximumHeight}.`);
|
|
541
|
+
}
|
|
542
|
+
if (
|
|
543
|
+
options.groundYRange &&
|
|
544
|
+
(bounds.min.y < options.groundYRange[0] || bounds.min.y > options.groundYRange[1])
|
|
545
|
+
) {
|
|
546
|
+
throw new Error(
|
|
547
|
+
`Baked GLB ground Y ${bounds.min.y} is outside ` +
|
|
548
|
+
`[${options.groundYRange[0]}, ${options.groundYRange[1]}].`,
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (options.expectedForward) {
|
|
552
|
+
let declared: unknown;
|
|
553
|
+
scene.traverse((object) => {
|
|
554
|
+
if (declared === undefined && object.userData['forward'] !== undefined) {
|
|
555
|
+
declared = object.userData['forward'];
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
if (!Array.isArray(declared) || declared.length !== 3) {
|
|
559
|
+
throw new Error('Baked GLB is missing a three-component forward declaration in extras.');
|
|
560
|
+
}
|
|
561
|
+
const actual = new THREE.Vector3(Number(declared[0]), Number(declared[1]), Number(declared[2]));
|
|
562
|
+
const expected = new THREE.Vector3(...options.expectedForward);
|
|
563
|
+
if (
|
|
564
|
+
!Number.isFinite(actual.lengthSq()) ||
|
|
565
|
+
actual.lengthSq() === 0 ||
|
|
566
|
+
expected.lengthSq() === 0 ||
|
|
567
|
+
actual.normalize().dot(expected.normalize()) < 1 - 1e-6
|
|
568
|
+
) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`Baked GLB forward declaration [${declared.join(', ')}] does not match ` +
|
|
571
|
+
`[${options.expectedForward.join(', ')}].`,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export async function validateExportedGlb(
|
|
578
|
+
bytes: Uint8Array,
|
|
579
|
+
options: GlbValidationOptions,
|
|
580
|
+
): Promise<GlbValidationResult> {
|
|
581
|
+
const arrayBuffer = new ArrayBuffer(bytes.byteLength);
|
|
582
|
+
new Uint8Array(arrayBuffer).set(bytes);
|
|
583
|
+
const gltf = await new GLTFLoader().parseAsync(arrayBuffer, '');
|
|
584
|
+
let meshCount = 0;
|
|
585
|
+
let skinnedMeshCount = 0;
|
|
586
|
+
let boneCount = 0;
|
|
587
|
+
try {
|
|
588
|
+
gltf.scene.traverse((object) => {
|
|
589
|
+
const mesh = object as THREE.Mesh;
|
|
590
|
+
if (mesh.isMesh) meshCount += 1;
|
|
591
|
+
if ((object as THREE.SkinnedMesh).isSkinnedMesh) skinnedMeshCount += 1;
|
|
592
|
+
if ((object as THREE.Bone).isBone) boneCount += 1;
|
|
593
|
+
assertFiniteValues(`Object '${object.name}' transform`, [
|
|
594
|
+
object.position.x,
|
|
595
|
+
object.position.y,
|
|
596
|
+
object.position.z,
|
|
597
|
+
object.quaternion.x,
|
|
598
|
+
object.quaternion.y,
|
|
599
|
+
object.quaternion.z,
|
|
600
|
+
object.quaternion.w,
|
|
601
|
+
object.scale.x,
|
|
602
|
+
object.scale.y,
|
|
603
|
+
object.scale.z,
|
|
604
|
+
...object.matrix.elements,
|
|
605
|
+
]);
|
|
606
|
+
const quaternionLength = object.quaternion.length();
|
|
607
|
+
if (quaternionLength === 0 || Math.abs(quaternionLength - 1) > 1e-3) {
|
|
608
|
+
throw new Error(`Object '${object.name}' has a non-normalized quaternion.`);
|
|
609
|
+
}
|
|
610
|
+
if (mesh.isMesh) validateGeometry(mesh);
|
|
611
|
+
if ((object as THREE.SkinnedMesh).isSkinnedMesh) {
|
|
612
|
+
validateSkin(object as THREE.SkinnedMesh);
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
if (meshCount === 0) throw new Error('Baked GLB round-trip contained no meshes.');
|
|
616
|
+
if (options.requireSkinned && skinnedMeshCount === 0) {
|
|
617
|
+
throw new Error('Baked GLB round-trip contained no SkinnedMesh.');
|
|
618
|
+
}
|
|
619
|
+
if (options.forbidSkinned && skinnedMeshCount !== 0) {
|
|
620
|
+
throw new Error('Rigid articulated output unexpectedly contains a SkinnedMesh.');
|
|
621
|
+
}
|
|
622
|
+
if (options.expectedBoneCount !== undefined && boneCount !== options.expectedBoneCount) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`Baked GLB round-trip carries ${boneCount} bones; the source rig has ` +
|
|
625
|
+
`${options.expectedBoneCount}.`,
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
if (options.vertexColorSample) validateVertexColorSample(gltf.scene, options.vertexColorSample);
|
|
629
|
+
if (options.requiredUvChannels?.length) {
|
|
630
|
+
validateUvChannels(gltf.scene, options.requiredUvChannels);
|
|
631
|
+
}
|
|
632
|
+
if (options.materialMapSample) validateMaterialMapSample(gltf.scene, options.materialMapSample);
|
|
633
|
+
if (options.requiredMorphTargets?.length) {
|
|
634
|
+
validateMorphTargets(gltf.scene, options.requiredMorphTargets);
|
|
635
|
+
}
|
|
636
|
+
const clips = gltf.animations.map((clip) => clip.name);
|
|
637
|
+
for (const clip of gltf.animations) validateClip(clip);
|
|
638
|
+
const clipSet = new Set(clips);
|
|
639
|
+
const missing = options.requiredClips.filter((name) => !clipSet.has(name));
|
|
640
|
+
if (missing.length > 0) throw new Error(`Baked GLB is missing clips: ${missing.join(', ')}`);
|
|
641
|
+
const animatedNodes = new Set(
|
|
642
|
+
gltf.animations.flatMap((clip) =>
|
|
643
|
+
clip.tracks.map((track) => track.name.slice(0, track.name.lastIndexOf('.'))),
|
|
644
|
+
),
|
|
645
|
+
);
|
|
646
|
+
const animatedNodeCount = animatedNodes.size;
|
|
647
|
+
if (animatedNodeCount < (options.minimumAnimatedNodes ?? 0)) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
`Baked GLB animates ${animatedNodeCount} nodes; expected at least ${options.minimumAnimatedNodes}.`,
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
validateBoundsAndForward(gltf.scene, options);
|
|
653
|
+
return { meshCount, skinnedMeshCount, boneCount, animatedNodeCount, clips };
|
|
654
|
+
} finally {
|
|
655
|
+
disposeObject3D(gltf.scene);
|
|
656
|
+
}
|
|
657
|
+
}
|