@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,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE FREEZE — the whole batching mechanism, as plain functions over a
|
|
3
|
+
* `THREE.Object3D`. `Frozen.tsx` is a twelve-line React wrapper around it, and
|
|
4
|
+
* everything worth testing is here, provable headlessly with no renderer and
|
|
5
|
+
* no React.
|
|
6
|
+
*
|
|
7
|
+
* ── WHAT IT DOES ────────────────────────────────────────────────────────────
|
|
8
|
+
* A game authored as TSX draws one mesh per JSX element. That is the right
|
|
9
|
+
* authoring model and the wrong SUBMISSION model: a hall built from a thousand
|
|
10
|
+
* named panels is a thousand draw calls, and at ~2.75 µs of CPU each, a
|
|
11
|
+
* four-thousand-mesh scene spends ~11 ms per frame doing nothing but telling
|
|
12
|
+
* the GPU what to draw. {@link freezeSubtree} collapses that mechanically:
|
|
13
|
+
*
|
|
14
|
+
* 1. walk the subtree, keeping the meshes that are safe to collapse
|
|
15
|
+
* (`staticBatchSkipReason`, in the engine — see ELIGIBILITY below);
|
|
16
|
+
* 2. group them by STRUCTURAL material identity plus the shadow flags —
|
|
17
|
+
* by what the draw LOOKS like, never by `material.uuid` (see THE UUID
|
|
18
|
+
* TRAP below, which is the whole reason this is not a ten-line helper);
|
|
19
|
+
* 3. inside a group, members whose geometry is also structurally identical
|
|
20
|
+
* become ONE `InstancedMesh`; the rest — same material, different shapes
|
|
21
|
+
* — become ONE merged mesh;
|
|
22
|
+
* 4. the originals are HIDDEN, not removed. React still owns them, their
|
|
23
|
+
* names still resolve for `render.census` and the editor hierarchy, and
|
|
24
|
+
* {@link restoreFrozen} puts everything back exactly as it was.
|
|
25
|
+
*
|
|
26
|
+
* ── THE UUID TRAP ───────────────────────────────────────────────────────────
|
|
27
|
+
* The obvious grouping key is `material.uuid`. It is also the key that batches
|
|
28
|
+
* NOTHING in a freshly scaffolded world, because inline JSX materials —
|
|
29
|
+
* `<meshStandardMaterial color="#8a6a44" />` inside a `.map()` — construct one
|
|
30
|
+
* material object PER MESH. Five hundred identical crates are five hundred
|
|
31
|
+
* uuids. A uuid-keyed freeze reports "no groups found", renders correctly, and
|
|
32
|
+
* is indistinguishable from a correct freeze over an unbatchable scene. The
|
|
33
|
+
* key here is `materialMergeSignature` from `@volter/threejs-runtime/render/structural-
|
|
34
|
+
* signature` — the SAME module the engine's own batcher and the draw-call
|
|
35
|
+
* advisor use, so all three agree about what "the same draw" means.
|
|
36
|
+
*
|
|
37
|
+
* ── ELIGIBILITY: DEGRADE, NEVER BREAK ───────────────────────────────────────
|
|
38
|
+
* Anything the engine's `staticBatchSkipReason` refuses — instanced, batched,
|
|
39
|
+
* skinned, morph-target, transparent, or explicitly opted out — stays exactly
|
|
40
|
+
* as it was. Multi-material meshes are the one capability-owned refinement:
|
|
41
|
+
* every geometry group becomes a material-specific candidate, so batching
|
|
42
|
+
* preserves the renderer's ordered slot assignment instead of flattening it.
|
|
43
|
+
* A malformed group, missing slot, or transparent slot refuses the WHOLE mesh
|
|
44
|
+
* before any original is hidden. A wrapper placed over a subtree that turns
|
|
45
|
+
* out to be mostly ineligible loses nothing and says so on the console
|
|
46
|
+
* ({@link freezeSubtree}'s `skipped` tally). It never leaves the graph
|
|
47
|
+
* half-collapsed.
|
|
48
|
+
*
|
|
49
|
+
* ── THE CONTRACT, WHICH IS THE PART A COMPILER CANNOT CHECK ─────────────────
|
|
50
|
+
* Children must be MOUNT-STATIC. Nothing under a freeze may move, re-colour,
|
|
51
|
+
* unmount or conditionally appear afterwards: the visible pixels come from the
|
|
52
|
+
* batched products, so a change to a hidden original simply never shows.
|
|
53
|
+
* Reactive scenery belongs outside the wrapper. That contract used to live
|
|
54
|
+
* only in a docblock, which is where contracts go to be broken silently —
|
|
55
|
+
* `mutation-watch.ts` is the tripwire that now says so out loud.
|
|
56
|
+
*
|
|
57
|
+
* ── RESOURCE OWNERSHIP, STATED ONCE ─────────────────────────────────────────
|
|
58
|
+
* OWNER: the {@link FrozenBatch} handle {@link freezeSubtree} returns. It owns
|
|
59
|
+
* exactly the objects it CREATED — the merged geometries and the product
|
|
60
|
+
* meshes — and nothing else. It does NOT own the source geometries or the
|
|
61
|
+
* materials: those belong to the game's own component tree, are only borrowed
|
|
62
|
+
* by the products, and disposing them here would tear the originals apart.
|
|
63
|
+
* SHARERS: `Frozen.tsx` (one handle per mounted wrapper) and
|
|
64
|
+
* `mutation-watch.ts` (reads the hidden originals). TEARDOWN:
|
|
65
|
+
* {@link restoreFrozen}, the ONE path that ends a freeze — it un-hides every
|
|
66
|
+
* original, removes every product, and disposes only the merged geometries it
|
|
67
|
+
* created. Idempotent.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
import { markBuiltInternal } from '@volter/threejs-runtime/adapter/hierarchy-marks';
|
|
71
|
+
import {
|
|
72
|
+
geometryLayoutSignature,
|
|
73
|
+
geometrySignature,
|
|
74
|
+
materialMergeSignature,
|
|
75
|
+
type StaticBatchSkipReason,
|
|
76
|
+
staticBatchSkipReason,
|
|
77
|
+
} from '@volter/threejs-runtime/render/structural-signature';
|
|
78
|
+
import * as THREE from 'three';
|
|
79
|
+
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
|
|
80
|
+
|
|
81
|
+
/** Marks a mesh this module CREATED, so a nested or repeated freeze walks past
|
|
82
|
+
* its own output instead of batching a batch. */
|
|
83
|
+
export const FROZEN_PRODUCT_KEY = 'staticBatchProduct';
|
|
84
|
+
|
|
85
|
+
/** One original, and the state a freeze changed on it. */
|
|
86
|
+
export interface FrozenSource {
|
|
87
|
+
readonly mesh: THREE.Mesh;
|
|
88
|
+
/** `visible` as the game set it, restored verbatim — a freeze must not
|
|
89
|
+
* un-hide something the game had deliberately hidden. */
|
|
90
|
+
readonly wasVisible: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** What one freeze produced and what it needs to undo itself. */
|
|
94
|
+
export interface FrozenBatch {
|
|
95
|
+
/** The wrapper's name, for every message about this freeze. */
|
|
96
|
+
readonly name: string;
|
|
97
|
+
/** The node the products were added to. */
|
|
98
|
+
readonly root: THREE.Object3D;
|
|
99
|
+
/** The meshes this freeze created — instanced and merged products. */
|
|
100
|
+
readonly products: readonly THREE.Mesh[];
|
|
101
|
+
/** Product geometries this freeze created. Instanced products usually
|
|
102
|
+
* borrow source geometry, but a material-group instance owns its extracted
|
|
103
|
+
* group geometry too. Every entry is disposed exactly once on restore. */
|
|
104
|
+
readonly ownedGeometries: readonly THREE.BufferGeometry[];
|
|
105
|
+
/** The originals it hid, in walk order. */
|
|
106
|
+
readonly sources: readonly FrozenSource[];
|
|
107
|
+
/** Draws submitted by the members BEFORE (one per source) and AFTER (one per
|
|
108
|
+
* product). The reduction this freeze actually achieved, measured rather
|
|
109
|
+
* than assumed. */
|
|
110
|
+
readonly drawsBefore: number;
|
|
111
|
+
readonly drawsAfter: number;
|
|
112
|
+
/** Eligible meshes the freeze refused, by reason. `not-a-mesh` is never
|
|
113
|
+
* counted: a `<group>` is not a refusal. */
|
|
114
|
+
readonly skipped: Readonly<Partial<Record<FrozenSkipReason, number>>>;
|
|
115
|
+
/** A representative refused node per reason, so a warning can name one. */
|
|
116
|
+
readonly skippedExample: Readonly<Partial<Record<FrozenSkipReason, string>>>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Capability-specific refusals extend the engine's node-level answer with
|
|
120
|
+
* the one invalid shape only group extraction can see. */
|
|
121
|
+
export type FrozenSkipReason = StaticBatchSkipReason | 'invalid-material-groups';
|
|
122
|
+
|
|
123
|
+
const activeBatches = new Set<FrozenBatch>();
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Publish a mounted freeze to render-side consumers. `freezeSubtree` stays a pure mechanical
|
|
127
|
+
* operation: its React owners activate the handle only for the interval in which the declaration
|
|
128
|
+
* is mounted, and the returned cleanup is the one path that retires it.
|
|
129
|
+
*/
|
|
130
|
+
export function activateFrozenBatch(batch: FrozenBatch): () => void {
|
|
131
|
+
activeBatches.add(batch);
|
|
132
|
+
let active = true;
|
|
133
|
+
return () => {
|
|
134
|
+
if (!active) return;
|
|
135
|
+
active = false;
|
|
136
|
+
activeBatches.delete(batch);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Live mount-static regions. The wrapper itself remains mobile, so consumers must observe it. */
|
|
141
|
+
export function activeFrozenBatches(): readonly FrozenBatch[] {
|
|
142
|
+
return [...activeBatches];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const UNNAMED = '(unnamed)';
|
|
146
|
+
|
|
147
|
+
function address(node: THREE.Object3D): string {
|
|
148
|
+
return `${node.parent?.name || UNNAMED}/${node.name || UNNAMED}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Collapse every batchable mesh under `root` into as few draws as the
|
|
153
|
+
* structure allows, and answer the handle that undoes it.
|
|
154
|
+
*
|
|
155
|
+
* The products are added to `root` itself and baked into ROOT-LOCAL space, so
|
|
156
|
+
* moving the wrapper still moves the whole frozen assembly as one — a freeze
|
|
157
|
+
* takes away per-child mobility, not the wrapper's own.
|
|
158
|
+
*/
|
|
159
|
+
export function freezeSubtree(root: THREE.Object3D, name = 'Frozen'): FrozenBatch {
|
|
160
|
+
root.updateWorldMatrix(true, true);
|
|
161
|
+
const toLocal = new THREE.Matrix4().copy(root.matrixWorld).invert();
|
|
162
|
+
|
|
163
|
+
interface Candidate {
|
|
164
|
+
mesh: THREE.Mesh;
|
|
165
|
+
material: THREE.Material;
|
|
166
|
+
/** Full source geometry for ordinary meshes; one physically extracted
|
|
167
|
+
* renderable group for a material-array mesh. */
|
|
168
|
+
geometry: THREE.BufferGeometry;
|
|
169
|
+
/** Extracted group geometries belong to this freeze until transferred to
|
|
170
|
+
* a product or disposed after a merge. */
|
|
171
|
+
ownsGeometry: boolean;
|
|
172
|
+
/** A material-array source must be replaced even when this candidate is a
|
|
173
|
+
* singleton; otherwise hiding the source drops that group's pixels. */
|
|
174
|
+
requiresReplacement: boolean;
|
|
175
|
+
/** Same shape, instanceable as one geometry. */
|
|
176
|
+
geometryKey: string;
|
|
177
|
+
/** Which vertex attributes it carries — `mergeGeometries` refuses a batch
|
|
178
|
+
* whose members disagree, so this is what the merge path groups by. */
|
|
179
|
+
layoutKey: string;
|
|
180
|
+
}
|
|
181
|
+
const groups = new Map<string, Candidate[]>();
|
|
182
|
+
const skipped: Partial<Record<FrozenSkipReason, number>> = {};
|
|
183
|
+
const skippedExample: Partial<Record<FrozenSkipReason, string>> = {};
|
|
184
|
+
const temporaryGeometries = new Set<THREE.BufferGeometry>();
|
|
185
|
+
|
|
186
|
+
interface GeometryGroup {
|
|
187
|
+
readonly start: number;
|
|
188
|
+
readonly count: number;
|
|
189
|
+
readonly materialIndex?: number | undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const groupRange = (
|
|
193
|
+
geometry: THREE.BufferGeometry,
|
|
194
|
+
group: GeometryGroup,
|
|
195
|
+
): { start: number; count: number } | null => {
|
|
196
|
+
const elements = geometry.getIndex()?.count ?? geometry.getAttribute('position')?.count ?? 0;
|
|
197
|
+
if (
|
|
198
|
+
!Number.isInteger(group.start) ||
|
|
199
|
+
!Number.isInteger(group.count) ||
|
|
200
|
+
group.start < 0 ||
|
|
201
|
+
group.count < 0 ||
|
|
202
|
+
group.start + group.count > elements
|
|
203
|
+
) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const drawStart = geometry.drawRange.start;
|
|
207
|
+
const drawCount = geometry.drawRange.count;
|
|
208
|
+
const drawEnd = drawCount === Number.POSITIVE_INFINITY ? elements : drawStart + drawCount;
|
|
209
|
+
if (
|
|
210
|
+
!Number.isInteger(drawStart) ||
|
|
211
|
+
drawStart < 0 ||
|
|
212
|
+
(drawCount !== Number.POSITIVE_INFINITY && (!Number.isInteger(drawCount) || drawCount < 0)) ||
|
|
213
|
+
(!Number.isInteger(drawEnd) && drawEnd !== Number.POSITIVE_INFINITY)
|
|
214
|
+
) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const start = Math.max(group.start, drawStart);
|
|
218
|
+
const end = Math.min(group.start + group.count, drawEnd, elements);
|
|
219
|
+
return end <= start ? { start, count: 0 } : { start, count: end - start };
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const extractGroup = (
|
|
223
|
+
geometry: THREE.BufferGeometry,
|
|
224
|
+
range: { start: number; count: number },
|
|
225
|
+
): THREE.BufferGeometry | null => {
|
|
226
|
+
const extracted = geometry.clone();
|
|
227
|
+
extracted.clearGroups();
|
|
228
|
+
extracted.setDrawRange(0, Number.POSITIVE_INFINITY);
|
|
229
|
+
const index = geometry.getIndex();
|
|
230
|
+
if (index) {
|
|
231
|
+
const values = index.array.slice(range.start, range.start + range.count);
|
|
232
|
+
extracted.setIndex(new THREE.BufferAttribute(values, index.itemSize, index.normalized));
|
|
233
|
+
return extracted;
|
|
234
|
+
}
|
|
235
|
+
for (const [attributeName, attribute] of Object.entries(geometry.attributes)) {
|
|
236
|
+
if (
|
|
237
|
+
'isInterleavedBufferAttribute' in attribute &&
|
|
238
|
+
attribute.isInterleavedBufferAttribute === true
|
|
239
|
+
) {
|
|
240
|
+
extracted.dispose();
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
if (attribute.count < range.start + range.count) {
|
|
244
|
+
extracted.dispose();
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const values = attribute.array.slice(
|
|
248
|
+
range.start * attribute.itemSize,
|
|
249
|
+
(range.start + range.count) * attribute.itemSize,
|
|
250
|
+
);
|
|
251
|
+
extracted.setAttribute(
|
|
252
|
+
attributeName,
|
|
253
|
+
new THREE.BufferAttribute(values, attribute.itemSize, attribute.normalized),
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return extracted;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const addCandidate = (candidate: Candidate): void => {
|
|
260
|
+
const key = `${materialMergeSignature(candidate.material)}|${candidate.mesh.castShadow ? 1 : 0}${candidate.mesh.receiveShadow ? 1 : 0}|layers:${candidate.mesh.layers.mask}`;
|
|
261
|
+
const existing = groups.get(key);
|
|
262
|
+
if (existing) existing.push(candidate);
|
|
263
|
+
else groups.set(key, [candidate]);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const multiMaterialCandidates = (mesh: THREE.Mesh): Candidate[] | null => {
|
|
267
|
+
const materials = mesh.material as THREE.Material[];
|
|
268
|
+
const geometry = mesh.geometry as THREE.BufferGeometry;
|
|
269
|
+
if (
|
|
270
|
+
materials.length === 0 ||
|
|
271
|
+
materials.some((material) => !material?.isMaterial || material.transparent) ||
|
|
272
|
+
geometry.groups.length === 0
|
|
273
|
+
) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const candidates: Candidate[] = [];
|
|
277
|
+
const refuse = (): null => {
|
|
278
|
+
for (const candidate of candidates) {
|
|
279
|
+
temporaryGeometries.delete(candidate.geometry);
|
|
280
|
+
candidate.geometry.dispose();
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
};
|
|
284
|
+
for (const group of geometry.groups) {
|
|
285
|
+
const materialIndex = group.materialIndex ?? 0;
|
|
286
|
+
const range = groupRange(geometry, group);
|
|
287
|
+
if (
|
|
288
|
+
!Number.isInteger(materialIndex) ||
|
|
289
|
+
materialIndex < 0 ||
|
|
290
|
+
materialIndex >= materials.length
|
|
291
|
+
) {
|
|
292
|
+
return refuse();
|
|
293
|
+
}
|
|
294
|
+
if (range === null) {
|
|
295
|
+
return refuse();
|
|
296
|
+
}
|
|
297
|
+
if (range.count === 0) continue;
|
|
298
|
+
const extracted = extractGroup(geometry, range);
|
|
299
|
+
if (!extracted) {
|
|
300
|
+
return refuse();
|
|
301
|
+
}
|
|
302
|
+
temporaryGeometries.add(extracted);
|
|
303
|
+
const layoutKey = geometryLayoutSignature(extracted);
|
|
304
|
+
candidates.push({
|
|
305
|
+
mesh,
|
|
306
|
+
material: materials[materialIndex] as THREE.Material,
|
|
307
|
+
geometry: extracted,
|
|
308
|
+
ownsGeometry: true,
|
|
309
|
+
requiresReplacement: true,
|
|
310
|
+
geometryKey: `${geometrySignature(geometry)}|group:${range.start}:${range.count}|${layoutKey}`,
|
|
311
|
+
layoutKey,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (candidates.length === 0) return null;
|
|
315
|
+
return candidates;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const walk = (node: THREE.Object3D): void => {
|
|
319
|
+
if (node.userData[FROZEN_PRODUCT_KEY] === true) return;
|
|
320
|
+
// Hierarchical visibility: a hidden parent hides its subtree, and a freeze
|
|
321
|
+
// that swallowed the children of a hidden group would make them appear.
|
|
322
|
+
if (!node.visible) return;
|
|
323
|
+
const reason = staticBatchSkipReason(node);
|
|
324
|
+
if (reason === 'opted-out') {
|
|
325
|
+
skipped['opted-out'] = (skipped['opted-out'] ?? 0) + 1;
|
|
326
|
+
skippedExample['opted-out'] ??= address(node);
|
|
327
|
+
return; // the opt-out covers the whole subtree
|
|
328
|
+
}
|
|
329
|
+
if (reason === null) {
|
|
330
|
+
const mesh = node as THREE.Mesh;
|
|
331
|
+
const material = mesh.material as THREE.Material;
|
|
332
|
+
const geometry = mesh.geometry as THREE.BufferGeometry;
|
|
333
|
+
const layoutKey = geometryLayoutSignature(geometry);
|
|
334
|
+
const candidate: Candidate = {
|
|
335
|
+
mesh,
|
|
336
|
+
material,
|
|
337
|
+
geometry,
|
|
338
|
+
ownsGeometry: false,
|
|
339
|
+
requiresReplacement: false,
|
|
340
|
+
// The layout rides in the instancing key too: two geometries can be
|
|
341
|
+
// structurally identical primitives and still disagree about, say, a
|
|
342
|
+
// second UV set added downstream.
|
|
343
|
+
geometryKey: `${geometrySignature(geometry)}|${layoutKey}`,
|
|
344
|
+
layoutKey,
|
|
345
|
+
};
|
|
346
|
+
addCandidate(candidate);
|
|
347
|
+
} else if (reason === 'multi-material') {
|
|
348
|
+
const mesh = node as THREE.Mesh;
|
|
349
|
+
const candidates = multiMaterialCandidates(mesh);
|
|
350
|
+
if (candidates) {
|
|
351
|
+
for (const candidate of candidates) addCandidate(candidate);
|
|
352
|
+
} else {
|
|
353
|
+
const invalidReason: FrozenSkipReason =
|
|
354
|
+
Array.isArray(mesh.material) && mesh.material.some((material) => material?.transparent)
|
|
355
|
+
? 'transparent'
|
|
356
|
+
: 'invalid-material-groups';
|
|
357
|
+
skipped[invalidReason] = (skipped[invalidReason] ?? 0) + 1;
|
|
358
|
+
skippedExample[invalidReason] ??= address(node);
|
|
359
|
+
}
|
|
360
|
+
} else if (reason !== 'not-a-mesh') {
|
|
361
|
+
skipped[reason] = (skipped[reason] ?? 0) + 1;
|
|
362
|
+
skippedExample[reason] ??= address(node);
|
|
363
|
+
}
|
|
364
|
+
for (const child of node.children) walk(child);
|
|
365
|
+
};
|
|
366
|
+
for (const child of root.children) walk(child);
|
|
367
|
+
|
|
368
|
+
const products: THREE.Mesh[] = [];
|
|
369
|
+
const sources: FrozenSource[] = [];
|
|
370
|
+
const sourceRecords = new Map<THREE.Mesh, FrozenSource>();
|
|
371
|
+
const ownedGeometries = new Set<THREE.BufferGeometry>();
|
|
372
|
+
const relative = new THREE.Matrix4();
|
|
373
|
+
let drawsBefore = 0;
|
|
374
|
+
|
|
375
|
+
const adopt = (product: THREE.Mesh, members: Candidate[], label: string): void => {
|
|
376
|
+
product.name = `${name} · ${label}`;
|
|
377
|
+
product.castShadow = members[0]?.mesh.castShadow ?? false;
|
|
378
|
+
product.receiveShadow = members[0]?.mesh.receiveShadow ?? false;
|
|
379
|
+
product.layers.mask = members[0]?.mesh.layers.mask ?? 1;
|
|
380
|
+
product.matrixAutoUpdate = false;
|
|
381
|
+
product.userData[FROZEN_PRODUCT_KEY] = true;
|
|
382
|
+
// A product is MACHINERY, not content: the hierarchy's job is to show the scenery the author
|
|
383
|
+
// wrote, and the originals are still there under their own names to be selected and inspected.
|
|
384
|
+
// Without this mark a freeze that collapsed 800 panels into two draws would ADD two rows to the
|
|
385
|
+
// panel it was supposed to make cheaper — `hierarchy-marks` names this exact case ("the
|
|
386
|
+
// instanced-mesh pools behind a tile field").
|
|
387
|
+
markBuiltInternal(product);
|
|
388
|
+
root.add(product);
|
|
389
|
+
products.push(product);
|
|
390
|
+
for (const member of members) {
|
|
391
|
+
drawsBefore += 1;
|
|
392
|
+
if (!sourceRecords.has(member.mesh)) {
|
|
393
|
+
const source = { mesh: member.mesh, wasVisible: member.mesh.visible };
|
|
394
|
+
sourceRecords.set(member.mesh, source);
|
|
395
|
+
sources.push(source);
|
|
396
|
+
}
|
|
397
|
+
member.mesh.visible = false;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const transferGeometry = (geometry: THREE.BufferGeometry): void => {
|
|
402
|
+
temporaryGeometries.delete(geometry);
|
|
403
|
+
ownedGeometries.add(geometry);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const disposeCandidateGeometry = (candidate: Candidate): void => {
|
|
407
|
+
if (!candidate.ownsGeometry || !temporaryGeometries.delete(candidate.geometry)) return;
|
|
408
|
+
candidate.geometry.dispose();
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const adoptSingleton = (member: Candidate): void => {
|
|
412
|
+
const geometry = member.geometry;
|
|
413
|
+
relative.multiplyMatrices(toLocal, member.mesh.matrixWorld);
|
|
414
|
+
geometry.applyMatrix4(relative);
|
|
415
|
+
geometry.computeBoundingSphere();
|
|
416
|
+
transferGeometry(geometry);
|
|
417
|
+
adopt(new THREE.Mesh(geometry, member.material), [member], `group ${productNo++}`);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
let productNo = 0;
|
|
421
|
+
for (const members of groups.values()) {
|
|
422
|
+
if (members.length < 2) {
|
|
423
|
+
const member = members[0];
|
|
424
|
+
if (member?.requiresReplacement) adoptSingleton(member);
|
|
425
|
+
continue; // one ordinary mesh is already one draw
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Same material AND same geometry → instancing, which keeps ONE copy of
|
|
429
|
+
// the vertices on the GPU. Different geometries under the same material
|
|
430
|
+
// have nothing to share but the shader, so they merge.
|
|
431
|
+
const byGeometry = new Map<string, Candidate[]>();
|
|
432
|
+
for (const member of members) {
|
|
433
|
+
const bucket = byGeometry.get(member.geometryKey);
|
|
434
|
+
if (bucket) bucket.push(member);
|
|
435
|
+
else byGeometry.set(member.geometryKey, [member]);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const heterogeneous: Candidate[] = [];
|
|
439
|
+
for (const bucket of byGeometry.values()) {
|
|
440
|
+
if (bucket.length < 2) {
|
|
441
|
+
heterogeneous.push(...bucket);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const prototype = bucket[0] as Candidate;
|
|
445
|
+
const instanced = new THREE.InstancedMesh(
|
|
446
|
+
prototype.geometry,
|
|
447
|
+
prototype.material,
|
|
448
|
+
bucket.length,
|
|
449
|
+
);
|
|
450
|
+
bucket.forEach((member, index) => {
|
|
451
|
+
relative.multiplyMatrices(toLocal, member.mesh.matrixWorld);
|
|
452
|
+
instanced.setMatrixAt(index, relative);
|
|
453
|
+
});
|
|
454
|
+
instanced.instanceMatrix.needsUpdate = true;
|
|
455
|
+
instanced.computeBoundingSphere();
|
|
456
|
+
if (prototype.ownsGeometry) transferGeometry(prototype.geometry);
|
|
457
|
+
for (const member of bucket.slice(1)) disposeCandidateGeometry(member);
|
|
458
|
+
adopt(instanced, bucket, `instanced ${productNo++}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// The leftovers merge, but only WITHIN one attribute layout.
|
|
462
|
+
// `mergeGeometries` answers `null` for a batch whose members disagree
|
|
463
|
+
// about attributes, and one such member in the pile would take the whole
|
|
464
|
+
// merge down with it — a wrapper that batched nothing because one panel
|
|
465
|
+
// carried a second UV set is the silent failure this splits apart.
|
|
466
|
+
const byLayout = new Map<string, Candidate[]>();
|
|
467
|
+
for (const member of heterogeneous) {
|
|
468
|
+
const bucket = byLayout.get(member.layoutKey);
|
|
469
|
+
if (bucket) bucket.push(member);
|
|
470
|
+
else byLayout.set(member.layoutKey, [member]);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
for (const bucket of byLayout.values()) {
|
|
474
|
+
if (bucket.length < 2) {
|
|
475
|
+
const member = bucket[0];
|
|
476
|
+
if (member?.requiresReplacement) adoptSingleton(member);
|
|
477
|
+
continue; // an ordinary lone leftover is already one draw
|
|
478
|
+
}
|
|
479
|
+
const parts = bucket.map((member) => {
|
|
480
|
+
const geometry = member.geometry.clone();
|
|
481
|
+
relative.multiplyMatrices(toLocal, member.mesh.matrixWorld);
|
|
482
|
+
geometry.applyMatrix4(relative);
|
|
483
|
+
return geometry;
|
|
484
|
+
});
|
|
485
|
+
const merged = mergeGeometries(parts, false);
|
|
486
|
+
for (const part of parts) part.dispose();
|
|
487
|
+
// Still `null`? Then they were incompatible for a reason the layout
|
|
488
|
+
// signature does not see. Ordinary sources stay untouched; group
|
|
489
|
+
// candidates fall back to one replacement apiece so hiding their source
|
|
490
|
+
// can never make pixels disappear.
|
|
491
|
+
if (!merged) {
|
|
492
|
+
for (const member of bucket) {
|
|
493
|
+
if (member.requiresReplacement) adoptSingleton(member);
|
|
494
|
+
}
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
merged.computeBoundingSphere();
|
|
498
|
+
ownedGeometries.add(merged);
|
|
499
|
+
for (const member of bucket) disposeCandidateGeometry(member);
|
|
500
|
+
adopt(
|
|
501
|
+
new THREE.Mesh(merged, (bucket[0] as Candidate).material),
|
|
502
|
+
bucket,
|
|
503
|
+
`merged ${productNo++}`,
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Defensive ownership closure: every extracted group is either a product's
|
|
509
|
+
// geometry, consumed by a merge, or disposed here. No refused/failed path
|
|
510
|
+
// may strand GPU buffers.
|
|
511
|
+
for (const geometry of temporaryGeometries) geometry.dispose();
|
|
512
|
+
temporaryGeometries.clear();
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
name,
|
|
516
|
+
root,
|
|
517
|
+
products,
|
|
518
|
+
ownedGeometries: [...ownedGeometries],
|
|
519
|
+
sources,
|
|
520
|
+
drawsBefore,
|
|
521
|
+
drawsAfter: products.length,
|
|
522
|
+
skipped,
|
|
523
|
+
skippedExample,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Idempotency without mutating the handle: the batch is a record of what
|
|
528
|
+
* happened, and a record that empties itself cannot be inspected afterwards. */
|
|
529
|
+
const alreadyRestored = new WeakSet<FrozenBatch>();
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Undo a freeze: every original visible again exactly as the game left it,
|
|
533
|
+
* every product removed and its merged geometry disposed. Idempotent — the
|
|
534
|
+
* second call finds nothing to do.
|
|
535
|
+
*
|
|
536
|
+
* Only geometries this freeze CREATED are disposed. An ordinary instanced
|
|
537
|
+
* product borrows the prototype's source geometry; a group-instanced product
|
|
538
|
+
* owns its extracted geometry and lists it in `ownedGeometries`. Every product
|
|
539
|
+
* borrows its material from the live component tree.
|
|
540
|
+
*/
|
|
541
|
+
export function restoreFrozen(batch: FrozenBatch): void {
|
|
542
|
+
if (alreadyRestored.has(batch)) return;
|
|
543
|
+
alreadyRestored.add(batch);
|
|
544
|
+
for (const source of batch.sources) source.mesh.visible = source.wasVisible;
|
|
545
|
+
for (const product of batch.products) {
|
|
546
|
+
batch.root.remove(product);
|
|
547
|
+
const instanced = product as THREE.InstancedMesh;
|
|
548
|
+
// `InstancedMesh.dispose()` releases the instance buffers only. Geometry
|
|
549
|
+
// ownership is handled uniformly by the batch record below.
|
|
550
|
+
if (instanced.isInstancedMesh) instanced.dispose();
|
|
551
|
+
}
|
|
552
|
+
for (const geometry of batch.ownedGeometries) geometry.dispose();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* The console line for a freeze that had to refuse members, or `null` when it
|
|
557
|
+
* refused nothing.
|
|
558
|
+
*
|
|
559
|
+
* Refusals are aggregated into ONE line per wrapper rather than one per mesh:
|
|
560
|
+
* a subtree of 800 transparent panels would otherwise produce 800 warnings and
|
|
561
|
+
* teach the reader to mute the channel. An OPTED-OUT member is never in the
|
|
562
|
+
* count — it is a declaration, not a surprise, and warning about it would
|
|
563
|
+
* punish exactly the discipline this asks for.
|
|
564
|
+
*/
|
|
565
|
+
export function describeFrozenSkips(batch: FrozenBatch): string | null {
|
|
566
|
+
const reasons = (Object.keys(batch.skipped) as FrozenSkipReason[])
|
|
567
|
+
.filter((reason) => reason !== 'opted-out')
|
|
568
|
+
.sort((a, b) => (batch.skipped[b] ?? 0) - (batch.skipped[a] ?? 0));
|
|
569
|
+
if (reasons.length === 0) return null;
|
|
570
|
+
const total = reasons.reduce((sum, reason) => sum + (batch.skipped[reason] ?? 0), 0);
|
|
571
|
+
const detail = reasons
|
|
572
|
+
.map((reason) => `${batch.skipped[reason]} ${reason} (e.g. ${batch.skippedExample[reason]})`)
|
|
573
|
+
.join(', ');
|
|
574
|
+
return (
|
|
575
|
+
`[static-batch] <Frozen name="${batch.name}"> left ${total} mesh(es) unbatched: ${detail}. ` +
|
|
576
|
+
'They still render — each is simply its own draw. Move them out of the wrapper, or declare ' +
|
|
577
|
+
'the exclusion where it is deliberate: userData={{ staticBatch: false }}.'
|
|
578
|
+
);
|
|
579
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STATIC BATCHING, in one wrapper.
|
|
3
|
+
*
|
|
4
|
+
* A TSX world draws one mesh per JSX element. Past a few hundred elements the
|
|
5
|
+
* frame stops being about pixels and starts being about SUBMISSION: ~2.75 µs
|
|
6
|
+
* of CPU per draw call, so a four-thousand-mesh scene burns ~11 ms telling the
|
|
7
|
+
* GPU what to draw before it draws anything. `<Frozen>` collapses a
|
|
8
|
+
* mount-static subtree into a handful of draws, with no change to how the
|
|
9
|
+
* scenery is authored:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* import { Frozen } from './lib/static-batch';
|
|
13
|
+
*
|
|
14
|
+
* <Frozen name="Terminal">{panels}</Frozen>
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Read `Frozen.tsx` for the contract (children must be mount-static) and
|
|
18
|
+
* `freeze.ts` for what gets collapsed, what is refused, and why the grouping
|
|
19
|
+
* key is structural rather than `material.uuid` — that last one is the trap
|
|
20
|
+
* this capability exists to keep a game out of.
|
|
21
|
+
*
|
|
22
|
+
* Nothing here runs at import: the wrapper does its work when it mounts.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export { Frozen, type FrozenProps } from './Frozen';
|
|
26
|
+
export {
|
|
27
|
+
activateFrozenBatch,
|
|
28
|
+
activeFrozenBatches,
|
|
29
|
+
describeFrozenSkips,
|
|
30
|
+
FROZEN_PRODUCT_KEY,
|
|
31
|
+
type FrozenBatch,
|
|
32
|
+
type FrozenSource,
|
|
33
|
+
freezeSubtree,
|
|
34
|
+
restoreFrozen,
|
|
35
|
+
} from './freeze';
|
|
36
|
+
export {
|
|
37
|
+
type FrozenMutationWatch,
|
|
38
|
+
MUTATION_SAMPLE_MS,
|
|
39
|
+
watchFrozenMutations,
|
|
40
|
+
} from './mutation-watch';
|
|
41
|
+
export {
|
|
42
|
+
createFrozenDirectionalShadowController,
|
|
43
|
+
type FrozenDirectionalShadowController,
|
|
44
|
+
} from './shadow-cache';
|