@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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B8.4 — pure horizontal-slice measurement math for
|
|
3
|
+
* `project.humanoid.slice-measure`: cut a world-space triangle soup at a
|
|
4
|
+
* height, fit an axis-aligned ellipse to the cross-section, and sample the
|
|
5
|
+
* outline radially so the residual shape (relative to that ellipse) can be
|
|
6
|
+
* pasted into a body spec as a profile suggestion.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately three.js-free (plain numbers in flat arrays) so every formula
|
|
9
|
+
* here is unit-testable against exact synthetic outlines — see
|
|
10
|
+
* `packages/editor/test/humanoid-slice-measure.test.ts`.
|
|
11
|
+
*
|
|
12
|
+
* Conventions (matching the humanoid body engine's ring vocabulary):
|
|
13
|
+
* - `hx`/`hz` are half-extents along +X / +Z; `ox`/`oz` the slice center.
|
|
14
|
+
* - Radial angle θ starts at +Z (the character's FRONT once the model is
|
|
15
|
+
* yaw-normalized to face +Z) and turns toward +X: direction
|
|
16
|
+
* `(x, z) = (sin θ, cos θ)`.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface SliceEllipse {
|
|
20
|
+
/** Half-extent along X (metres, world space). */
|
|
21
|
+
hx: number;
|
|
22
|
+
/** Half-extent along Z. */
|
|
23
|
+
hz: number;
|
|
24
|
+
/** Slice-center offset along X. */
|
|
25
|
+
ox: number;
|
|
26
|
+
/** Slice-center offset along Z. */
|
|
27
|
+
oz: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Intersect a triangle soup with the horizontal plane `y`, returning flat
|
|
32
|
+
* cross-section segments `[x1, z1, x2, z2, ...]`.
|
|
33
|
+
*
|
|
34
|
+
* `triangles` is a flat xyz soup: 9 numbers per triangle. An edge crosses the
|
|
35
|
+
* plane when its endpoints land on opposite sides of the half-open split
|
|
36
|
+
* `y_vertex <= y` — a deterministic convention that also resolves vertices
|
|
37
|
+
* lying exactly on the plane without epsilon tuning.
|
|
38
|
+
*/
|
|
39
|
+
export function sliceTriangleSegments(triangles: ArrayLike<number>, y: number): number[] {
|
|
40
|
+
if (triangles.length % 9 !== 0) {
|
|
41
|
+
throw new Error(`Triangle soup length must be a multiple of 9, got ${triangles.length}.`);
|
|
42
|
+
}
|
|
43
|
+
const segments: number[] = [];
|
|
44
|
+
const crossX: number[] = [0, 0];
|
|
45
|
+
const crossZ: number[] = [0, 0];
|
|
46
|
+
for (let base = 0; base < triangles.length; base += 9) {
|
|
47
|
+
let crossings = 0;
|
|
48
|
+
for (let edge = 0; edge < 3 && crossings < 2; edge++) {
|
|
49
|
+
const a = base + edge * 3;
|
|
50
|
+
const b = base + ((edge + 1) % 3) * 3;
|
|
51
|
+
const ya = triangles[a + 1]!;
|
|
52
|
+
const yb = triangles[b + 1]!;
|
|
53
|
+
if (ya <= y === yb <= y) continue;
|
|
54
|
+
const t = (y - ya) / (yb - ya);
|
|
55
|
+
crossX[crossings] = triangles[a]! + (triangles[b]! - triangles[a]!) * t;
|
|
56
|
+
crossZ[crossings] = triangles[a + 2]! + (triangles[b + 2]! - triangles[a + 2]!) * t;
|
|
57
|
+
crossings++;
|
|
58
|
+
}
|
|
59
|
+
if (crossings === 2) {
|
|
60
|
+
segments.push(crossX[0]!, crossZ[0]!, crossX[1]!, crossZ[1]!);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return segments;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Axis-aligned ellipse fit from the cross-section's extents: center at the
|
|
68
|
+
* middle of the X/Z bounds, half-extents to those bounds — the same shape a
|
|
69
|
+
* body-spec ring row (`hx`, `hz`, center offset) describes.
|
|
70
|
+
*/
|
|
71
|
+
export function fitSliceEllipse(segments: ArrayLike<number>): SliceEllipse {
|
|
72
|
+
if (segments.length === 0 || segments.length % 4 !== 0) {
|
|
73
|
+
throw new Error(`Slice segments must be non-empty groups of 4, got ${segments.length}.`);
|
|
74
|
+
}
|
|
75
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
76
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
77
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
78
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
79
|
+
for (let index = 0; index < segments.length; index += 2) {
|
|
80
|
+
const x = segments[index]!;
|
|
81
|
+
const z = segments[index + 1]!;
|
|
82
|
+
if (x < minX) minX = x;
|
|
83
|
+
if (x > maxX) maxX = x;
|
|
84
|
+
if (z < minZ) minZ = z;
|
|
85
|
+
if (z > maxZ) maxZ = z;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
hx: (maxX - minX) / 2,
|
|
89
|
+
hz: (maxZ - minZ) / 2,
|
|
90
|
+
ox: (minX + maxX) / 2,
|
|
91
|
+
oz: (minZ + maxZ) / 2,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Polar radius of the axis-aligned ellipse at angle θ (θ=0 → +Z, toward
|
|
96
|
+
* +X): the r solving `(r sinθ / hx)² + (r cosθ / hz)² = 1`. */
|
|
97
|
+
export function ellipseRadiusAt(hx: number, hz: number, theta: number): number {
|
|
98
|
+
if (hx <= 0 || hz <= 0) {
|
|
99
|
+
throw new Error(`Ellipse radii must be positive (hx=${hx}, hz=${hz}).`);
|
|
100
|
+
}
|
|
101
|
+
const sin = Math.sin(theta);
|
|
102
|
+
const cos = Math.cos(theta);
|
|
103
|
+
return 1 / Math.sqrt((sin * sin) / (hx * hx) + (cos * cos) / (hz * hz));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Farthest intersection distance of the ray from `(ox, oz)` in direction
|
|
107
|
+
* `(sin θ, cos θ)` against the slice segments; 0 when nothing is hit. */
|
|
108
|
+
export function radialDistance(
|
|
109
|
+
segments: ArrayLike<number>,
|
|
110
|
+
ox: number,
|
|
111
|
+
oz: number,
|
|
112
|
+
theta: number,
|
|
113
|
+
): number {
|
|
114
|
+
const dirX = Math.sin(theta);
|
|
115
|
+
const dirZ = Math.cos(theta);
|
|
116
|
+
let farthest = 0;
|
|
117
|
+
for (let index = 0; index < segments.length; index += 4) {
|
|
118
|
+
const ax = segments[index]! - ox;
|
|
119
|
+
const az = segments[index + 1]! - oz;
|
|
120
|
+
const bx = segments[index + 2]! - ox;
|
|
121
|
+
const bz = segments[index + 3]! - oz;
|
|
122
|
+
const ex = bx - ax;
|
|
123
|
+
const ez = bz - az;
|
|
124
|
+
// Solve a + s·e = t·dir for s ∈ [0,1], t ≥ 0 via 2D cross products.
|
|
125
|
+
const denominator = dirX * ez - dirZ * ex;
|
|
126
|
+
if (Math.abs(denominator) < 1e-12) continue; // parallel (or degenerate) segment
|
|
127
|
+
const s = (dirZ * ax - dirX * az) / denominator;
|
|
128
|
+
if (s < 0 || s > 1) continue;
|
|
129
|
+
const t = Math.abs(dirX) >= Math.abs(dirZ) ? (ax + s * ex) / dirX : (az + s * ez) / dirZ;
|
|
130
|
+
if (t > farthest) farthest = t;
|
|
131
|
+
}
|
|
132
|
+
return farthest;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The residual outline: the slice's radial distance from the fitted-ellipse
|
|
137
|
+
* center at `count` uniform angles, each normalized by the ellipse's own
|
|
138
|
+
* radius at that angle. 1 everywhere ⇒ the slice IS the ellipse; >1 bulges
|
|
139
|
+
* past it (a square slice reads √2 at its corners); 0 ⇒ no outline hit on
|
|
140
|
+
* that ray (e.g. a hollow/degenerate direction).
|
|
141
|
+
*/
|
|
142
|
+
export function radialResiduals(
|
|
143
|
+
segments: ArrayLike<number>,
|
|
144
|
+
ellipse: SliceEllipse,
|
|
145
|
+
count = 16,
|
|
146
|
+
): number[] {
|
|
147
|
+
if (!Number.isInteger(count) || count < 4) {
|
|
148
|
+
throw new Error(`Radial sample count must be an integer >= 4, got ${count}.`);
|
|
149
|
+
}
|
|
150
|
+
const residuals: number[] = [];
|
|
151
|
+
for (let index = 0; index < count; index++) {
|
|
152
|
+
const theta = (index * 2 * Math.PI) / count;
|
|
153
|
+
const radius = radialDistance(segments, ellipse.ox, ellipse.oz, theta);
|
|
154
|
+
residuals.push(radius / ellipseRadiusAt(ellipse.hx, ellipse.hz, theta));
|
|
155
|
+
}
|
|
156
|
+
return residuals;
|
|
157
|
+
}
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `node-image` — the image substrate three's GLTF exporter and loader
|
|
3
|
+
* assume a BROWSER provides, supplied for the headless bake.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS FILE EXISTS. A material map only reaches a GLB through
|
|
6
|
+
* `GLTFExporter.processImage`, which draws the texture into a 2D canvas
|
|
7
|
+
* and asks that canvas for PNG bytes; the re-import reverses it, fetching
|
|
8
|
+
* those bytes as a blob and handing them to `createImageBitmap`. Node has
|
|
9
|
+
* neither a canvas nor an image decoder, so without this the bake either
|
|
10
|
+
* throws (`document is not defined`) or — worse — silently exports a
|
|
11
|
+
* material with no texture at all.
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT DELIBERATELY IS NOT. Not a canvas: every method below exists
|
|
14
|
+
* because a specific line of `GLTFExporter` calls it (`fillRect` and
|
|
15
|
+
* `drawImage` for the metalness/roughness composite, `putImageData` for
|
|
16
|
+
* the `DataTexture` path, `getImageData` for both), and anything else
|
|
17
|
+
* throws rather than pretending. Not an image library either: PNG is the
|
|
18
|
+
* only format, 8-bit RGBA the only layout, and the compression is the
|
|
19
|
+
* platform's own `CompressionStream`/`DecompressionStream` — the same
|
|
20
|
+
* deflate a `zlib` dependency would have called, available in Node and in
|
|
21
|
+
* browsers, so nothing here is a hand-rolled codec beyond PNG's chunk
|
|
22
|
+
* framing and scanline filters.
|
|
23
|
+
*
|
|
24
|
+
* DECODING FILE IMAGES (a PNG a game AUTHOR supplies, rather than one this
|
|
25
|
+
* module just wrote) is a strictly larger problem — palettes, 16-bit,
|
|
26
|
+
* interlacing, JPEG — and is out of scope by decision: procedural
|
|
27
|
+
* `DataTexture` sources only. The decoder below refuses anything it was
|
|
28
|
+
* not built for, by name, instead of guessing.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/** A decoded 8-bit RGBA raster: the shape `ImageData`, `DataTexture.image`
|
|
32
|
+
* and our `ImageBitmap` stand-in all share, and the only pixel layout
|
|
33
|
+
* anything in this file handles. */
|
|
34
|
+
export interface RgbaRaster {
|
|
35
|
+
readonly width: number;
|
|
36
|
+
readonly height: number;
|
|
37
|
+
readonly data: Uint8ClampedArray;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] as const;
|
|
41
|
+
|
|
42
|
+
const CRC_TABLE = (() => {
|
|
43
|
+
const table = new Uint32Array(256);
|
|
44
|
+
for (let n = 0; n < 256; n++) {
|
|
45
|
+
let c = n;
|
|
46
|
+
for (let k = 0; k < 8; k++) c = (c & 1) === 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
47
|
+
table[n] = c >>> 0;
|
|
48
|
+
}
|
|
49
|
+
return table;
|
|
50
|
+
})();
|
|
51
|
+
|
|
52
|
+
function crc32(bytes: Uint8Array): number {
|
|
53
|
+
let c = 0xffffffff;
|
|
54
|
+
for (const byte of bytes) c = (CRC_TABLE[(c ^ byte) & 0xff] as number) ^ (c >>> 8);
|
|
55
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A `Blob` over a copy of `bytes`. The copy is not incidental: a
|
|
59
|
+
* `Uint8Array`'s buffer is `ArrayBufferLike` (possibly SHARED), which is
|
|
60
|
+
* not a `BlobPart`, and a view into a larger buffer would otherwise hand
|
|
61
|
+
* the whole buffer over. */
|
|
62
|
+
function blobOf(bytes: Uint8Array, type?: string): Blob {
|
|
63
|
+
const copy = new Uint8Array(bytes.length);
|
|
64
|
+
copy.set(bytes);
|
|
65
|
+
return new Blob([copy.buffer], type === undefined ? undefined : { type });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function deflate(bytes: Uint8Array): Promise<Uint8Array> {
|
|
69
|
+
const stream = blobOf(bytes).stream().pipeThrough(new CompressionStream('deflate'));
|
|
70
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function inflate(bytes: Uint8Array): Promise<Uint8Array> {
|
|
74
|
+
const stream = blobOf(bytes).stream().pipeThrough(new DecompressionStream('deflate'));
|
|
75
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One PNG chunk: length, type, payload, CRC over type+payload. */
|
|
79
|
+
function chunk(type: string, payload: Uint8Array): Uint8Array {
|
|
80
|
+
const out = new Uint8Array(payload.length + 12);
|
|
81
|
+
const view = new DataView(out.buffer);
|
|
82
|
+
view.setUint32(0, payload.length);
|
|
83
|
+
for (let i = 0; i < 4; i++) out[4 + i] = type.charCodeAt(i);
|
|
84
|
+
out.set(payload, 8);
|
|
85
|
+
view.setUint32(payload.length + 8, crc32(out.subarray(4, payload.length + 8)));
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Encode an 8-bit RGBA raster as a PNG (color type 6, no interlacing,
|
|
90
|
+
* filter 0 on every scanline — the layout a rasterizer produces and the
|
|
91
|
+
* one glTF embeds). */
|
|
92
|
+
export async function encodePng(
|
|
93
|
+
raster: RgbaRaster,
|
|
94
|
+
options: { readonly alpha?: boolean } = {},
|
|
95
|
+
): Promise<Uint8Array> {
|
|
96
|
+
const { width, height, data } = raster;
|
|
97
|
+
// THREE CHANNELS OR FOUR. Blender writes colour type 2 for an RGB image
|
|
98
|
+
// setting and type 6 for RGBA, and a reader can tell: the two files differ in
|
|
99
|
+
// every byte. The incoming raster is always RGBA; dropping alpha is a copy,
|
|
100
|
+
// not a conversion.
|
|
101
|
+
const alpha = options.alpha !== false;
|
|
102
|
+
const channels = alpha ? 4 : 3;
|
|
103
|
+
const stride = width * channels;
|
|
104
|
+
const raw = new Uint8Array((stride + 1) * height);
|
|
105
|
+
for (let y = 0; y < height; y++) {
|
|
106
|
+
raw[y * (stride + 1)] = 0;
|
|
107
|
+
const at = y * (stride + 1) + 1;
|
|
108
|
+
if (alpha) {
|
|
109
|
+
raw.set(data.subarray(y * width * 4, (y + 1) * width * 4), at);
|
|
110
|
+
} else {
|
|
111
|
+
for (let x = 0; x < width; x++) {
|
|
112
|
+
const from = (y * width + x) * 4;
|
|
113
|
+
raw[at + x * 3] = data[from]!;
|
|
114
|
+
raw[at + x * 3 + 1] = data[from + 1]!;
|
|
115
|
+
raw[at + x * 3 + 2] = data[from + 2]!;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const header = new Uint8Array(13);
|
|
120
|
+
const headerView = new DataView(header.buffer);
|
|
121
|
+
headerView.setUint32(0, width);
|
|
122
|
+
headerView.setUint32(4, height);
|
|
123
|
+
header[8] = 8; // bit depth
|
|
124
|
+
header[9] = options.alpha !== false ? 6 : 2; // truecolour with alpha, or without
|
|
125
|
+
const parts = [
|
|
126
|
+
new Uint8Array(PNG_SIGNATURE),
|
|
127
|
+
chunk('IHDR', header),
|
|
128
|
+
chunk('IDAT', await deflate(raw)),
|
|
129
|
+
chunk('IEND', new Uint8Array(0)),
|
|
130
|
+
];
|
|
131
|
+
const png = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
132
|
+
let offset = 0;
|
|
133
|
+
for (const part of parts) {
|
|
134
|
+
png.set(part, offset);
|
|
135
|
+
offset += part.length;
|
|
136
|
+
}
|
|
137
|
+
return png;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Undo PNG's per-scanline filters in place (spec §9.2). Four bytes per
|
|
141
|
+
* pixel, so the "corresponding byte of the pixel to the left" is `x - 4`. */
|
|
142
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the five filter types are one spec table; splitting them hides the shared left/up/upLeft window.
|
|
143
|
+
function unfilter(raw: Uint8Array, width: number, height: number): Uint8ClampedArray {
|
|
144
|
+
const stride = width * 4;
|
|
145
|
+
const out = new Uint8ClampedArray(stride * height);
|
|
146
|
+
for (let y = 0; y < height; y++) {
|
|
147
|
+
const filter = raw[y * (stride + 1)] as number;
|
|
148
|
+
const source = y * (stride + 1) + 1;
|
|
149
|
+
const target = y * stride;
|
|
150
|
+
for (let x = 0; x < stride; x++) {
|
|
151
|
+
const value = raw[source + x] as number;
|
|
152
|
+
const left = x >= 4 ? (out[target + x - 4] as number) : 0;
|
|
153
|
+
const up = y > 0 ? (out[target - stride + x] as number) : 0;
|
|
154
|
+
const upLeft = y > 0 && x >= 4 ? (out[target - stride + x - 4] as number) : 0;
|
|
155
|
+
let restored: number;
|
|
156
|
+
if (filter === 0) restored = value;
|
|
157
|
+
else if (filter === 1) restored = value + left;
|
|
158
|
+
else if (filter === 2) restored = value + up;
|
|
159
|
+
else if (filter === 3) restored = value + ((left + up) >> 1);
|
|
160
|
+
else if (filter === 4) {
|
|
161
|
+
const p = left + up - upLeft;
|
|
162
|
+
const dLeft = Math.abs(p - left);
|
|
163
|
+
const dUp = Math.abs(p - up);
|
|
164
|
+
const dUpLeft = Math.abs(p - upLeft);
|
|
165
|
+
restored = value + (dLeft <= dUp && dLeft <= dUpLeft ? left : dUp <= dUpLeft ? up : upLeft);
|
|
166
|
+
} else throw new Error(`decodePng: unknown scanline filter ${filter} on row ${y}.`);
|
|
167
|
+
out[target + x] = restored & 0xff;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Decode an 8-bit RGBA PNG. Refuses — by name — every variant this
|
|
174
|
+
* deliberately does not implement, rather than returning wrong pixels. */
|
|
175
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one chunk walk plus the refusals it must make while walking; every branch is a named PNG variant.
|
|
176
|
+
export async function decodePng(bytes: Uint8Array): Promise<RgbaRaster> {
|
|
177
|
+
for (const [i, expected] of PNG_SIGNATURE.entries()) {
|
|
178
|
+
if (bytes[i] !== expected) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
'decodePng: not a PNG. The headless bake embeds and reads back PNG only; a JPEG or ' +
|
|
181
|
+
'another format needs a real image decoder, which this deliberately is not.',
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
186
|
+
let offset = 8;
|
|
187
|
+
let width = 0;
|
|
188
|
+
let height = 0;
|
|
189
|
+
const idat: Uint8Array[] = [];
|
|
190
|
+
while (offset < bytes.length) {
|
|
191
|
+
const length = view.getUint32(offset);
|
|
192
|
+
const type = String.fromCharCode(...bytes.subarray(offset + 4, offset + 8));
|
|
193
|
+
const payload = bytes.subarray(offset + 8, offset + 8 + length);
|
|
194
|
+
if (type === 'IHDR') {
|
|
195
|
+
width = view.getUint32(offset + 8);
|
|
196
|
+
height = view.getUint32(offset + 12);
|
|
197
|
+
const [depth, colorType, , , interlace] = [...payload.subarray(8, 13)];
|
|
198
|
+
if (depth !== 8 || colorType !== 6 || interlace !== 0) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`decodePng: only 8-bit RGBA non-interlaced PNG is supported (got bit depth ${depth}, ` +
|
|
201
|
+
`color type ${colorType}, interlace ${interlace}).`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
} else if (type === 'IDAT') idat.push(payload);
|
|
205
|
+
else if (type === 'IEND') break;
|
|
206
|
+
offset += length + 12;
|
|
207
|
+
}
|
|
208
|
+
if (width === 0 || height === 0) throw new Error('decodePng: PNG carries no IHDR dimensions.');
|
|
209
|
+
const packed = new Uint8Array(idat.reduce((total, part) => total + part.length, 0));
|
|
210
|
+
let packedOffset = 0;
|
|
211
|
+
for (const part of idat) {
|
|
212
|
+
packed.set(part, packedOffset);
|
|
213
|
+
packedOffset += part.length;
|
|
214
|
+
}
|
|
215
|
+
const raw = await inflate(packed);
|
|
216
|
+
const expected = (width * 4 + 1) * height;
|
|
217
|
+
if (raw.length < expected) {
|
|
218
|
+
throw new Error(`decodePng: IDAT holds ${raw.length} bytes, expected ${expected}.`);
|
|
219
|
+
}
|
|
220
|
+
return { width, height, data: unfilter(raw, width, height) };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** `ImageData`, as `GLTFExporter` uses it: a constructor over an existing
|
|
224
|
+
* RGBA buffer, and a `data`/`width`/`height` triple to read back. */
|
|
225
|
+
export class NodeImageData implements RgbaRaster {
|
|
226
|
+
readonly data: Uint8ClampedArray;
|
|
227
|
+
readonly width: number;
|
|
228
|
+
readonly height: number;
|
|
229
|
+
constructor(data: Uint8ClampedArray, width: number, height: number) {
|
|
230
|
+
this.data = data;
|
|
231
|
+
this.width = width;
|
|
232
|
+
this.height = height;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** `#rgb` / `#rrggbb` only — the exporter's one fill is the literal
|
|
237
|
+
* `'#00ffff'`, and a silently-misparsed color would be an invisible
|
|
238
|
+
* wrong-metalness bug. */
|
|
239
|
+
function parseCssHex(color: string): readonly [number, number, number] {
|
|
240
|
+
const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color);
|
|
241
|
+
const long = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
|
|
242
|
+
if (short)
|
|
243
|
+
return [0, 1, 2].map((i) =>
|
|
244
|
+
Number.parseInt(`${short[i + 1]}${short[i + 1]}`, 16),
|
|
245
|
+
) as unknown as readonly [number, number, number];
|
|
246
|
+
if (long) {
|
|
247
|
+
return [0, 1, 2].map((i) => Number.parseInt(long[i + 1] as string, 16)) as unknown as readonly [
|
|
248
|
+
number,
|
|
249
|
+
number,
|
|
250
|
+
number,
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
throw new Error(
|
|
254
|
+
`NodeCanvas2DContext: only '#rgb'/'#rrggbb' fill styles are supported, got '${color}'.`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The subset of `CanvasRenderingContext2D` the exporter calls. */
|
|
259
|
+
class NodeCanvas2DContext {
|
|
260
|
+
fillStyle = '#000000';
|
|
261
|
+
#canvas: NodeOffscreenCanvas;
|
|
262
|
+
/** `scale(1, -1)` + `translate(0, h)` is the exporter's flipY idiom. A
|
|
263
|
+
* real `putImageData` IGNORES the transform (spec: it writes device
|
|
264
|
+
* pixels), so only `drawImage` honours this — matching a browser
|
|
265
|
+
* exactly, including for the `DataTexture` path where the flip
|
|
266
|
+
* therefore does nothing. */
|
|
267
|
+
#flipY = false;
|
|
268
|
+
|
|
269
|
+
constructor(canvas: NodeOffscreenCanvas) {
|
|
270
|
+
this.#canvas = canvas;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
translate(): void {
|
|
274
|
+
// Recorded by `scale`; the exporter only ever translates to set up a flip.
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
scale(_x: number, y: number): void {
|
|
278
|
+
if (y < 0) this.#flipY = !this.#flipY;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
fillRect(x: number, y: number, width: number, height: number): void {
|
|
282
|
+
const [r, g, b] = parseCssHex(this.fillStyle);
|
|
283
|
+
const target = this.#canvas.pixels;
|
|
284
|
+
for (let row = Math.max(0, y); row < Math.min(this.#canvas.height, y + height); row++) {
|
|
285
|
+
for (
|
|
286
|
+
let column = Math.max(0, x);
|
|
287
|
+
column < Math.min(this.#canvas.width, x + width);
|
|
288
|
+
column++
|
|
289
|
+
) {
|
|
290
|
+
const offset = (row * this.#canvas.width + column) * 4;
|
|
291
|
+
target[offset] = r;
|
|
292
|
+
target[offset + 1] = g;
|
|
293
|
+
target[offset + 2] = b;
|
|
294
|
+
target[offset + 3] = 255;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
getImageData(x: number, y: number, width: number, height: number): NodeImageData {
|
|
300
|
+
const out = new Uint8ClampedArray(width * height * 4);
|
|
301
|
+
for (let row = 0; row < height; row++) {
|
|
302
|
+
const sourceRow = y + row;
|
|
303
|
+
if (sourceRow < 0 || sourceRow >= this.#canvas.height) continue;
|
|
304
|
+
for (let column = 0; column < width; column++) {
|
|
305
|
+
const sourceColumn = x + column;
|
|
306
|
+
if (sourceColumn < 0 || sourceColumn >= this.#canvas.width) continue;
|
|
307
|
+
const from = (sourceRow * this.#canvas.width + sourceColumn) * 4;
|
|
308
|
+
const to = (row * width + column) * 4;
|
|
309
|
+
for (let c = 0; c < 4; c++) out[to + c] = this.#canvas.pixels[from + c] as number;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return new NodeImageData(out, width, height);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
putImageData(image: RgbaRaster, dx: number, dy: number): void {
|
|
316
|
+
for (let row = 0; row < image.height; row++) {
|
|
317
|
+
const targetRow = dy + row;
|
|
318
|
+
if (targetRow < 0 || targetRow >= this.#canvas.height) continue;
|
|
319
|
+
for (let column = 0; column < image.width; column++) {
|
|
320
|
+
const targetColumn = dx + column;
|
|
321
|
+
if (targetColumn < 0 || targetColumn >= this.#canvas.width) continue;
|
|
322
|
+
const from = (row * image.width + column) * 4;
|
|
323
|
+
const to = (targetRow * this.#canvas.width + targetColumn) * 4;
|
|
324
|
+
for (let c = 0; c < 4; c++) this.#canvas.pixels[to + c] = image.data[from + c] as number;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Nearest-neighbour blit of any RGBA raster (a decoded bitmap or
|
|
330
|
+
* another canvas). The exporter only ever scales a whole image into a
|
|
331
|
+
* whole canvas, which is the metalness/roughness composite path. */
|
|
332
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: one nested sampling loop with its bounds and flip guards inline; extracting them costs a call per pixel.
|
|
333
|
+
drawImage(source: unknown, dx: number, dy: number, dw?: number, dh?: number): void {
|
|
334
|
+
const raster = source as Partial<RgbaRaster> & { pixels?: Uint8ClampedArray };
|
|
335
|
+
const data = raster.data ?? raster.pixels;
|
|
336
|
+
if (!data || !raster.width || !raster.height) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
'NodeCanvas2DContext.drawImage: only RGBA rasters produced by this module ' +
|
|
339
|
+
'(a decoded bitmap, a DataTexture image, or another canvas) can be drawn headlessly.',
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
const width = dw ?? raster.width;
|
|
343
|
+
const height = dh ?? raster.height;
|
|
344
|
+
for (let row = 0; row < height; row++) {
|
|
345
|
+
const targetRow = dy + (this.#flipY ? height - 1 - row : row);
|
|
346
|
+
if (targetRow < 0 || targetRow >= this.#canvas.height) continue;
|
|
347
|
+
const sourceRow = Math.min(raster.height - 1, Math.floor((row * raster.height) / height));
|
|
348
|
+
for (let column = 0; column < width; column++) {
|
|
349
|
+
const targetColumn = dx + column;
|
|
350
|
+
if (targetColumn < 0 || targetColumn >= this.#canvas.width) continue;
|
|
351
|
+
const sourceColumn = Math.min(
|
|
352
|
+
raster.width - 1,
|
|
353
|
+
Math.floor((column * raster.width) / width),
|
|
354
|
+
);
|
|
355
|
+
const from = (sourceRow * raster.width + sourceColumn) * 4;
|
|
356
|
+
const to = (targetRow * this.#canvas.width + targetColumn) * 4;
|
|
357
|
+
for (let c = 0; c < 4; c++) this.#canvas.pixels[to + c] = data[from + c] as number;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** `OffscreenCanvas`, as `GLTFExporter.getCanvas()` expects to find it when
|
|
364
|
+
* `document` is undefined: sized after construction, one 2D context, and
|
|
365
|
+
* `convertToBlob` for the PNG bytes that become the GLB's image
|
|
366
|
+
* bufferView. */
|
|
367
|
+
export class NodeOffscreenCanvas {
|
|
368
|
+
#width = 1;
|
|
369
|
+
#height = 1;
|
|
370
|
+
#pixels = new Uint8ClampedArray(4);
|
|
371
|
+
#context: NodeCanvas2DContext | undefined;
|
|
372
|
+
|
|
373
|
+
constructor(width = 1, height = 1) {
|
|
374
|
+
this.width = width;
|
|
375
|
+
this.height = height;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
get width(): number {
|
|
379
|
+
return this.#width;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
set width(value: number) {
|
|
383
|
+
this.#width = value;
|
|
384
|
+
this.#resize();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
get height(): number {
|
|
388
|
+
return this.#height;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
set height(value: number) {
|
|
392
|
+
this.#height = value;
|
|
393
|
+
this.#resize();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** The RGBA backing store, transparent black until something draws. */
|
|
397
|
+
get pixels(): Uint8ClampedArray {
|
|
398
|
+
return this.#pixels;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#resize(): void {
|
|
402
|
+
this.#pixels = new Uint8ClampedArray(Math.max(0, this.#width * this.#height * 4));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
getContext(kind: string): NodeCanvas2DContext {
|
|
406
|
+
if (kind !== '2d') {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`NodeOffscreenCanvas: only the '2d' context exists headlessly, got '${kind}'.`,
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
this.#context ??= new NodeCanvas2DContext(this);
|
|
412
|
+
return this.#context;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async convertToBlob(options: { type?: string } = {}): Promise<Blob> {
|
|
416
|
+
const type = options.type ?? 'image/png';
|
|
417
|
+
if (type !== 'image/png') {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`NodeOffscreenCanvas.convertToBlob: the headless bake encodes PNG only, got '${type}'.`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
const png = await encodePng({ width: this.#width, height: this.#height, data: this.#pixels });
|
|
423
|
+
return blobOf(png, type);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
let warnedAboutUndecodableImage = false;
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* `createImageBitmap`, as `ImageBitmapLoader` calls it on the re-import
|
|
431
|
+
* side: decode the blob and hand back something three can hang on a
|
|
432
|
+
* `Texture` — with its pixels still readable, which is what makes a
|
|
433
|
+
* round-trip texel assertion possible at all.
|
|
434
|
+
*
|
|
435
|
+
* An image this cannot decode (a JPEG in someone's downloaded clip-source
|
|
436
|
+
* GLB, say) REJECTS. `GLTFLoader` catches that and prints its own
|
|
437
|
+
* `Couldn't load texture <url>`, which says nothing about why, so the
|
|
438
|
+
* reason is printed once per process here. The alternative — the empty
|
|
439
|
+
* object this used to return — left the material holding a texture with
|
|
440
|
+
* nothing behind it, which is worse in every direction.
|
|
441
|
+
*/
|
|
442
|
+
export async function createImageBitmapFromBlob(
|
|
443
|
+
source: Blob | ArrayBuffer | Uint8Array,
|
|
444
|
+
): Promise<RgbaRaster & { close(): void }> {
|
|
445
|
+
const bytes =
|
|
446
|
+
source instanceof Uint8Array
|
|
447
|
+
? source
|
|
448
|
+
: new Uint8Array(source instanceof ArrayBuffer ? source : await source.arrayBuffer());
|
|
449
|
+
try {
|
|
450
|
+
const raster = await decodePng(bytes);
|
|
451
|
+
return { ...raster, close() {} };
|
|
452
|
+
} catch (error) {
|
|
453
|
+
if (!warnedAboutUndecodableImage) {
|
|
454
|
+
warnedAboutUndecodableImage = true;
|
|
455
|
+
console.warn(
|
|
456
|
+
'headless bake: an embedded image could not be decoded, so its texture is dropped on ' +
|
|
457
|
+
'import (the geometry and animations are unaffected). Only PNG is decodable outside a ' +
|
|
458
|
+
`browser — see lib/bake/node-image.ts. ${(error as Error).message}`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
}
|