@hraness/slopcamera 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { createBoundedJsonSnapshot } from "../../../src/code/json-snapshot";
|
|
4
|
+
import {
|
|
5
|
+
EvaluatedSpatialSceneSchema,
|
|
6
|
+
SpatialAssetIdSchema,
|
|
7
|
+
SpatialDigestSchema,
|
|
8
|
+
SpatialEntityIdSchema,
|
|
9
|
+
SpatialFrameRateSchema,
|
|
10
|
+
SpatialMaterialSchema,
|
|
11
|
+
SpatialMatrixSchema,
|
|
12
|
+
SpatialProjectionSchema,
|
|
13
|
+
SpatialTimeUsSchema,
|
|
14
|
+
type EvaluatedSpatialScene,
|
|
15
|
+
type SpatialCamera,
|
|
16
|
+
type SpatialEntity,
|
|
17
|
+
type SpatialProjection,
|
|
18
|
+
} from "../../../src/spatial-scene/contracts";
|
|
19
|
+
import { cameraMathView, composeTransform, invertTransform, multiplyTransforms, type Mat4 } from "../../../src/spatial-scene/math";
|
|
20
|
+
import { spatialAssetClosureDigests } from "../../../src/spatial-scene/identity";
|
|
21
|
+
import { canonicalJson, canonicalJsonSha256 } from "../core/canonical-json";
|
|
22
|
+
import {
|
|
23
|
+
HtmlOverlayAuthoringInputSchema,
|
|
24
|
+
HtmlOverlayDeclaredResourceSchema,
|
|
25
|
+
HtmlOverlayDeclaredResourcesSchema,
|
|
26
|
+
type HtmlOverlayDeclaredResource,
|
|
27
|
+
} from "./contracts";
|
|
28
|
+
import { htmlOverlayAssetLocalUrl, serializeHtmlOverlayImportMap } from "./libraries";
|
|
29
|
+
import { HtmlOverlayExecutionProfileSchema } from "./execution-profile";
|
|
30
|
+
import { SpatialSpzFactsSchema, SPATIAL_SPLAT_LIMITS, spatialSpzAllocationBounds } from "../contracts/spatial-world";
|
|
31
|
+
import { addSpatialSplatRuntime } from "./spatial-splat-runtime";
|
|
32
|
+
|
|
33
|
+
export const SPATIAL_OVERLAY_LIMITS = Object.freeze({
|
|
34
|
+
frames: 32,
|
|
35
|
+
requestBytes: 16 * 1024 * 1024,
|
|
36
|
+
requestValues: 1_000_000,
|
|
37
|
+
preparedAssets: 1_024,
|
|
38
|
+
primitivesPerAsset: 256,
|
|
39
|
+
verticesPerPrimitive: 65_536,
|
|
40
|
+
trianglesPerFrame: 100_000,
|
|
41
|
+
drawCallsPerFrame: 2_048,
|
|
42
|
+
decodedTexturePixels: 67_108_864,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const alphaSchema = z.enum(["straight", "opaque"]);
|
|
46
|
+
const textureShape = {
|
|
47
|
+
resource: HtmlOverlayDeclaredResourceSchema,
|
|
48
|
+
width: z.number().int().min(1).max(8_192),
|
|
49
|
+
height: z.number().int().min(1).max(8_192),
|
|
50
|
+
alpha: alphaSchema,
|
|
51
|
+
};
|
|
52
|
+
const samplerSchema = z.strictObject({
|
|
53
|
+
wrapS: z.union([z.literal(33071), z.literal(33648), z.literal(10497)]),
|
|
54
|
+
wrapT: z.union([z.literal(33071), z.literal(33648), z.literal(10497)]),
|
|
55
|
+
magFilter: z.union([z.literal(9728), z.literal(9729)]).optional(),
|
|
56
|
+
minFilter: z.union([z.literal(9728), z.literal(9729), z.literal(9984), z.literal(9985), z.literal(9986), z.literal(9987)]).optional(),
|
|
57
|
+
});
|
|
58
|
+
const finiteGeometry = z.number().finite().min(-1e6).max(1e6);
|
|
59
|
+
const primitiveSchema = z.strictObject({
|
|
60
|
+
positions: z.array(finiteGeometry).min(9).max(SPATIAL_OVERLAY_LIMITS.verticesPerPrimitive * 3),
|
|
61
|
+
normals: z.array(finiteGeometry).min(9).max(SPATIAL_OVERLAY_LIMITS.verticesPerPrimitive * 3).optional(),
|
|
62
|
+
uvs: z.array(finiteGeometry).min(6).max(SPATIAL_OVERLAY_LIMITS.verticesPerPrimitive * 2).optional(),
|
|
63
|
+
indices: z.array(z.number().int().min(0).max(SPATIAL_OVERLAY_LIMITS.verticesPerPrimitive - 1)).min(3)
|
|
64
|
+
.max(SPATIAL_OVERLAY_LIMITS.trianglesPerFrame * 3).optional(),
|
|
65
|
+
matrix: SpatialMatrixSchema,
|
|
66
|
+
sourceNodeIndex: z.number().int().min(0).max(65_535).optional(),
|
|
67
|
+
sourcePrimitiveIndex: z.number().int().min(0).max(65_535).optional(),
|
|
68
|
+
doubleSided: z.boolean().optional(),
|
|
69
|
+
alphaCutoff: z.number().finite().min(0).max(1).optional(),
|
|
70
|
+
alphaMode: z.enum(["OPAQUE", "MASK", "BLEND"]).optional(),
|
|
71
|
+
linearColor: z.tuple([z.number().finite().min(0).max(1), z.number().finite().min(0).max(1), z.number().finite().min(0).max(1)]).optional(),
|
|
72
|
+
/** Host-resolved final material; omitted means the entity's material. */
|
|
73
|
+
material: SpatialMaterialSchema.optional(),
|
|
74
|
+
texture: z.strictObject({ ...textureShape, flipY: z.boolean().optional(), sampler: samplerSchema.optional() }).optional(),
|
|
75
|
+
}).superRefine((primitive, context) => {
|
|
76
|
+
const vertices = primitive.positions.length / 3;
|
|
77
|
+
if (!Number.isInteger(vertices)
|
|
78
|
+
|| (primitive.indices === undefined ? vertices % 3 !== 0 : primitive.indices.length % 3 !== 0)
|
|
79
|
+
|| primitive.indices?.some(index => index >= vertices)
|
|
80
|
+
|| (primitive.normals !== undefined && primitive.normals.length !== primitive.positions.length)
|
|
81
|
+
|| (primitive.uvs !== undefined && primitive.uvs.length !== vertices * 2)
|
|
82
|
+
|| (primitive.texture !== undefined && primitive.uvs === undefined)) {
|
|
83
|
+
context.addIssue({ code: "custom", message: "Prepared geometry must contain complete triangles, matching attributes, and in-range indices; textured geometry requires UVs." });
|
|
84
|
+
}
|
|
85
|
+
try { invertTransform(primitive.matrix); } catch {
|
|
86
|
+
context.addIssue({ code: "custom", message: "Prepared geometry matrices must be finite invertible affine transforms." });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const PreparedSpatialAssetSchema = z.discriminatedUnion("kind", [
|
|
91
|
+
z.strictObject({ kind: z.literal("splat"), assetId: SpatialAssetIdSchema, entityId: SpatialEntityIdSchema, assetManifestSha256: SpatialDigestSchema,
|
|
92
|
+
resource: HtmlOverlayDeclaredResourceSchema, facts: SpatialSpzFactsSchema }),
|
|
93
|
+
z.strictObject({
|
|
94
|
+
kind: z.literal("raster"),
|
|
95
|
+
assetId: SpatialAssetIdSchema,
|
|
96
|
+
assetManifestSha256: SpatialDigestSchema,
|
|
97
|
+
entityId: SpatialEntityIdSchema,
|
|
98
|
+
/** null is a static surface. Video frames must name the exact snapshot time. */
|
|
99
|
+
timeUs: SpatialTimeUsSchema.nullable(),
|
|
100
|
+
sourceTimeUs: SpatialTimeUsSchema.optional(),
|
|
101
|
+
sourceFrameIndex: z.number().int().min(0).max(216_000).optional(),
|
|
102
|
+
sourcePresentationTimeUs: SpatialTimeUsSchema.optional(),
|
|
103
|
+
sourcePts: z.number().int().safe().min(0).optional(),
|
|
104
|
+
sourceTimeBase: z.strictObject({ numerator: z.string().regex(/^[1-9]\d{0,15}$/u), denominator: z.string().regex(/^[1-9]\d{0,15}$/u) }).optional(),
|
|
105
|
+
sourceTimestamp: z.string().max(64).regex(/^\d+(?:\.\d{1,9})?$/u).optional(),
|
|
106
|
+
sourceExactTimeUs: z.strictObject({ numerator: z.string().regex(/^(?:0|[1-9]\d{0,23})$/u), denominator: z.string().regex(/^[1-9]\d{0,8}$/u) }).optional(),
|
|
107
|
+
entityContentSha256: SpatialDigestSchema.optional(),
|
|
108
|
+
...textureShape,
|
|
109
|
+
}),
|
|
110
|
+
z.strictObject({
|
|
111
|
+
kind: z.literal("geometry"),
|
|
112
|
+
assetId: SpatialAssetIdSchema,
|
|
113
|
+
entityId: SpatialEntityIdSchema,
|
|
114
|
+
entityGeometrySha256: SpatialDigestSchema,
|
|
115
|
+
assetManifestSha256: SpatialDigestSchema,
|
|
116
|
+
nodeIndex: z.number().int().min(0).max(65_535).optional(),
|
|
117
|
+
timeUs: SpatialTimeUsSchema.nullable(),
|
|
118
|
+
primitives: z.array(primitiveSchema).min(1).max(SPATIAL_OVERLAY_LIMITS.primitivesPerAsset),
|
|
119
|
+
/** Optional exact canonical primitives JSON, served through the private resource boundary. */
|
|
120
|
+
resource: HtmlOverlayDeclaredResourceSchema.optional(),
|
|
121
|
+
}),
|
|
122
|
+
]);
|
|
123
|
+
|
|
124
|
+
export const SpatialCoverageSchema = z.discriminatedUnion("kind", [
|
|
125
|
+
z.strictObject({ kind: z.literal("opaque") }),
|
|
126
|
+
z.strictObject({ kind: z.literal("alpha-threshold"), threshold: z.number().finite().gt(0).max(1) }),
|
|
127
|
+
]);
|
|
128
|
+
export const SpatialRenderModeSchema = z.discriminatedUnion("kind", [
|
|
129
|
+
z.strictObject({ kind: z.literal("beauty") }),
|
|
130
|
+
z.strictObject({ kind: z.literal("object-id"), coverage: SpatialCoverageSchema }),
|
|
131
|
+
z.strictObject({ kind: z.literal("axial-depth"), coverage: SpatialCoverageSchema }),
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
export const SpatialOverlayBatchInputSchema = z.strictObject({
|
|
135
|
+
snapshots: z.array(EvaluatedSpatialSceneSchema).min(1).max(SPATIAL_OVERLAY_LIMITS.frames),
|
|
136
|
+
frameRate: SpatialFrameRateSchema,
|
|
137
|
+
mode: SpatialRenderModeSchema,
|
|
138
|
+
executionProfile: HtmlOverlayExecutionProfileSchema.optional(),
|
|
139
|
+
preparedAssets: z.array(PreparedSpatialAssetSchema).max(SPATIAL_OVERLAY_LIMITS.preparedAssets).default([]),
|
|
140
|
+
});
|
|
141
|
+
type DeepReadonly<T> = T extends object ? { readonly [Key in keyof T]: DeepReadonly<T[Key]> } : T;
|
|
142
|
+
export type PreparedSpatialAsset = DeepReadonly<z.infer<typeof PreparedSpatialAssetSchema>>;
|
|
143
|
+
export type SpatialRenderMode = DeepReadonly<z.infer<typeof SpatialRenderModeSchema>>;
|
|
144
|
+
export type SpatialOverlayBatchInput = DeepReadonly<z.input<typeof SpatialOverlayBatchInputSchema>>;
|
|
145
|
+
type PreparedRaster = Extract<PreparedSpatialAsset, { kind: "raster" }>;
|
|
146
|
+
type PreparedGeometry = Extract<PreparedSpatialAsset, { kind: "geometry" }>;
|
|
147
|
+
type PreparedSplat = Extract<PreparedSpatialAsset, { kind: "splat" }>;
|
|
148
|
+
type LoweredSplat = Readonly<{ kind: "splat"; entityId: string; key: string; matrix: Mat4 }>;
|
|
149
|
+
type Material = DeepReadonly<z.infer<typeof SpatialMaterialSchema>>;
|
|
150
|
+
type Texture = DeepReadonly<z.infer<typeof primitiveSchema>["texture"]>;
|
|
151
|
+
|
|
152
|
+
/** Text preparation draws white glyphs; final color is applied by the scene material. */
|
|
153
|
+
export function spatialTextRasterContentSha256(entity: Extract<SpatialEntity, { kind: "text" }>): string {
|
|
154
|
+
return canonicalJsonSha256({ domain: "slopcamera.spatial-text-raster.v1", text: entity.text, fontAssetId: entity.fontAssetId,
|
|
155
|
+
fontSize: entity.fontSize, width: entity.width, align: entity.align });
|
|
156
|
+
}
|
|
157
|
+
export function spatialGeometryContentSha256(entity: Extract<SpatialEntity, { kind: "mesh" }>): string {
|
|
158
|
+
return canonicalJsonSha256({ domain: "slopcamera.spatial-geometry-binding.v1", geometry: entity.geometry });
|
|
159
|
+
}
|
|
160
|
+
export function spatialVideoRasterContentSha256(entity: Extract<SpatialEntity, { kind: "video" }>): string {
|
|
161
|
+
return canonicalJsonSha256({ domain: "slopcamera.spatial-video-binding.v1", assetId: entity.assetId, sourceOffsetUs: entity.sourceOffsetUs, playback: entity.playback });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class SpatialOverlayCapabilityError extends Error {
|
|
165
|
+
readonly code = "unsupported-capability";
|
|
166
|
+
constructor(readonly capability: string, message: string) {
|
|
167
|
+
super(message);
|
|
168
|
+
this.name = "SpatialOverlayCapabilityError";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Exact byte-order encoding. Zero RGB with zero alpha is always no-hit. */
|
|
173
|
+
export function spatialSelectionColor(selectionId: number): readonly [number, number, number] {
|
|
174
|
+
if (!Number.isInteger(selectionId) || selectionId < 1 || selectionId > 4_096) {
|
|
175
|
+
throw new RangeError("Selection IDs must be integers in [1,4096].");
|
|
176
|
+
}
|
|
177
|
+
return Object.freeze([Math.floor(selectionId / 65_536), Math.floor(selectionId / 256) % 256, selectionId % 256]);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const SPATIAL_DEPTH_MAX_CODE = 16_777_215;
|
|
181
|
+
/** Decode an RGBA8 sample; byte values are data, never color-converted. */
|
|
182
|
+
export function decodeSpatialAxialDepth(rgba: readonly number[], near: number, far: number): number | null {
|
|
183
|
+
if (rgba.length !== 4 || rgba.some(byte => !Number.isInteger(byte) || byte < 0 || byte > 255)
|
|
184
|
+
|| !Number.isFinite(near) || !Number.isFinite(far) || near <= 0 || far <= near) {
|
|
185
|
+
throw new RangeError("Expected RGBA8 bytes and positive ordered clipping distances.");
|
|
186
|
+
}
|
|
187
|
+
const code = rgba[0]! * 65_536 + rgba[1]! * 256 + rgba[2]!;
|
|
188
|
+
if (rgba[3] === 0 && code === 0) return null;
|
|
189
|
+
if (rgba[3] !== 255 || code === 0) throw new RangeError("Malformed axial-depth validity or code.");
|
|
190
|
+
return Math.min(far, Math.max(near, near + (code - 1) / (SPATIAL_DEPTH_MAX_CODE - 1) * (far - near)));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** WebGL clip projection, with unequal focal lengths and off-center principal point. */
|
|
194
|
+
export function spatialWebGlProjection(projection: SpatialProjection): Mat4 {
|
|
195
|
+
const p = SpatialProjectionSchema.parse(projection);
|
|
196
|
+
if (p.kind === "perspective") {
|
|
197
|
+
return Object.freeze([
|
|
198
|
+
2 * p.fx / p.width, 0, 0, 0,
|
|
199
|
+
0, 2 * p.fy / p.height, 0, 0,
|
|
200
|
+
1 - 2 * p.cx / p.width, 2 * p.cy / p.height - 1, -(p.far + p.near) / (p.far - p.near), -1,
|
|
201
|
+
0, 0, -2 * p.far * p.near / (p.far - p.near), 0,
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
return Object.freeze([
|
|
205
|
+
2 / (p.right - p.left), 0, 0, 0,
|
|
206
|
+
0, 2 / (p.top - p.bottom), 0, 0,
|
|
207
|
+
0, 0, -2 / (p.far - p.near), 0,
|
|
208
|
+
-(p.right + p.left) / (p.right - p.left), -(p.top + p.bottom) / (p.top - p.bottom),
|
|
209
|
+
-(p.far + p.near) / (p.far - p.near), 1,
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
type LoweredMesh = Readonly<{
|
|
214
|
+
kind: "mesh";
|
|
215
|
+
entityId: string;
|
|
216
|
+
selectionId: number;
|
|
217
|
+
matrix: Mat4;
|
|
218
|
+
placement: SpatialEntity["placement"];
|
|
219
|
+
geometry: Exclude<Extract<SpatialEntity, { kind: "mesh" }>["geometry"], { kind: "asset" }> | { readonly kind: "prepared"; readonly key: string; readonly primitive: number };
|
|
220
|
+
material: Material;
|
|
221
|
+
textureName?: string;
|
|
222
|
+
textureAlpha?: "straight" | "opaque";
|
|
223
|
+
uvScale: readonly [number, number];
|
|
224
|
+
uvOffset: readonly [number, number];
|
|
225
|
+
surfaceScale: readonly [number, number];
|
|
226
|
+
flipViewUv: boolean;
|
|
227
|
+
doubleSided: boolean;
|
|
228
|
+
alphaCutoff: number;
|
|
229
|
+
alphaMode?: "OPAQUE" | "MASK" | "BLEND";
|
|
230
|
+
linearColor?: readonly [number, number, number];
|
|
231
|
+
textureOptions?: Readonly<{ readonly flipY?: boolean; readonly sampler?: DeepReadonly<z.infer<typeof samplerSchema>> }>;
|
|
232
|
+
}>;
|
|
233
|
+
type LoweredLight = Readonly<{
|
|
234
|
+
kind: "light";
|
|
235
|
+
entityId: string;
|
|
236
|
+
matrix: Mat4;
|
|
237
|
+
light: "ambient" | "point" | "directional";
|
|
238
|
+
color: string;
|
|
239
|
+
intensity: number;
|
|
240
|
+
}>;
|
|
241
|
+
|
|
242
|
+
function unsupported(capability: string, message: string): never {
|
|
243
|
+
throw new SpatialOverlayCapabilityError(capability, message);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function preparedKey(asset: PreparedSpatialAsset): string {
|
|
247
|
+
if (asset.kind === "splat") return `${asset.assetId}:${asset.entityId}:splat`;
|
|
248
|
+
return asset.kind === "raster"
|
|
249
|
+
? `${asset.assetId}:${asset.entityId}:${asset.timeUs === null ? "static" : String(asset.timeUs)}`
|
|
250
|
+
: `${asset.assetId}:${asset.entityId}:${asset.nodeIndex === undefined ? "all" : String(asset.nodeIndex)}:${asset.timeUs === null ? "static" : String(asset.timeUs)}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function assertRasterResource(texture: NonNullable<Texture>): void {
|
|
254
|
+
if (texture.resource.mediaType !== "image/png" && texture.resource.mediaType !== "image/jpeg") {
|
|
255
|
+
unsupported("prepared-raster-format", "Prepared surfaces must be validated PNG or JPEG resources; SVG, video, fonts, and diagram JSON require host preparation.");
|
|
256
|
+
}
|
|
257
|
+
if (texture.resource.bytes === 0) throw new RangeError("Prepared raster resources must be nonempty.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function assertCoverage(mode: SpatialRenderMode, material: Material, texture: Texture, alphaMode?: "OPAQUE" | "MASK" | "BLEND"): void {
|
|
261
|
+
if (mode.kind !== "beauty" && mode.coverage.kind === "opaque"
|
|
262
|
+
&& alphaMode !== "MASK"
|
|
263
|
+
&& (material.opacity !== 1 || texture?.alpha === "straight")) {
|
|
264
|
+
unsupported("opaque-coverage", "Opaque auxiliary passes require opaque materials and opaque textures. Select an explicit alpha threshold for translucent surfaces.");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function fitSurface(width: number, height: number, raster: PreparedRaster, fit: "contain" | "cover" | "stretch") {
|
|
269
|
+
const aspect = raster.width / raster.height;
|
|
270
|
+
const target = width / height;
|
|
271
|
+
let surfaceScale: [number, number] = [1, 1];
|
|
272
|
+
let uvScale: [number, number] = [1, 1];
|
|
273
|
+
if (fit === "contain") surfaceScale = aspect > target ? [1, target / aspect] : [aspect / target, 1];
|
|
274
|
+
if (fit === "cover") uvScale = aspect > target ? [target / aspect, 1] : [1, aspect / target];
|
|
275
|
+
return { surfaceScale, uvScale, uvOffset: [(1 - uvScale[0]) / 2, (1 - uvScale[1]) / 2] as const };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function geometryTriangleCount(mesh: LoweredMesh, geometries: ReadonlyMap<string, PreparedGeometry>): number {
|
|
279
|
+
switch (mesh.geometry.kind) {
|
|
280
|
+
case "box": return 12;
|
|
281
|
+
case "plane": return 2;
|
|
282
|
+
case "sphere": return 960;
|
|
283
|
+
case "cylinder": return 128;
|
|
284
|
+
case "prepared": {
|
|
285
|
+
const primitive = geometries.get(mesh.geometry.key)!.primitives[mesh.geometry.primitive]!;
|
|
286
|
+
return primitive.indices === undefined ? primitive.positions.length / 9 : primitive.indices.length / 3;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function escapeEmbeddedJson(value: unknown): string {
|
|
292
|
+
return canonicalJson(value).replaceAll("<", "\\u003c").replaceAll(">", "\\u003e")
|
|
293
|
+
.replaceAll("&", "\\u0026").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function calibratedCamera(camera: SpatialCamera) {
|
|
297
|
+
return {
|
|
298
|
+
cameraToWorld: cameraMathView(camera).cameraToWorld,
|
|
299
|
+
projection: spatialWebGlProjection(camera.projection),
|
|
300
|
+
near: camera.projection.near,
|
|
301
|
+
far: camera.projection.far,
|
|
302
|
+
kind: camera.projection.kind,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Pure lowering. The integer overlay clock transports frame indices only. The
|
|
308
|
+
* exact scene times and rational delivery rate remain in the returned metadata.
|
|
309
|
+
* No file reads, loaders, generator execution, or GPU work occur here.
|
|
310
|
+
*/
|
|
311
|
+
export function createSpatialOverlayBatch(input: unknown) {
|
|
312
|
+
const captured = createBoundedJsonSnapshot(input, SPATIAL_OVERLAY_LIMITS.requestBytes, "Spatial overlay request", {
|
|
313
|
+
maximumDepth: 48, maximumValues: SPATIAL_OVERLAY_LIMITS.requestValues,
|
|
314
|
+
});
|
|
315
|
+
const request = SpatialOverlayBatchInputSchema.parse(captured.value);
|
|
316
|
+
const first = request.snapshots[0]!;
|
|
317
|
+
const width = first.camera.projection.width;
|
|
318
|
+
const height = first.camera.projection.height;
|
|
319
|
+
const prepared = new Map<string, PreparedSpatialAsset>();
|
|
320
|
+
const geometries = new Map<string, PreparedGeometry>();
|
|
321
|
+
const splats = new Map<string, PreparedSplat>();
|
|
322
|
+
const resources = new Map<string, HtmlOverlayDeclaredResource>();
|
|
323
|
+
const textures = new Map<string, { readonly width: number; readonly height: number; readonly alpha: "straight" | "opaque" }>();
|
|
324
|
+
const usedPrepared = new Set<string>();
|
|
325
|
+
const bindTexture = (texture: NonNullable<Texture>): void => {
|
|
326
|
+
assertRasterResource(texture);
|
|
327
|
+
const existing = resources.get(texture.resource.name);
|
|
328
|
+
if (existing !== undefined && canonicalJson(existing) !== canonicalJson(texture.resource)) {
|
|
329
|
+
throw new RangeError("Prepared texture resource names must identify one immutable resource.");
|
|
330
|
+
}
|
|
331
|
+
const dimensions = { width: texture.width, height: texture.height, alpha: texture.alpha };
|
|
332
|
+
const priorDimensions = textures.get(texture.resource.name);
|
|
333
|
+
if (priorDimensions !== undefined && canonicalJson(priorDimensions) !== canonicalJson(dimensions)) {
|
|
334
|
+
throw new RangeError("Prepared texture dimensions and alpha semantics must agree for one resource.");
|
|
335
|
+
}
|
|
336
|
+
textures.set(texture.resource.name, dimensions);
|
|
337
|
+
resources.set(texture.resource.name, texture.resource);
|
|
338
|
+
};
|
|
339
|
+
for (const asset of request.preparedAssets) {
|
|
340
|
+
const key = preparedKey(asset);
|
|
341
|
+
if (prepared.has(key)) throw new RangeError(`Duplicate prepared asset binding: ${key}`);
|
|
342
|
+
prepared.set(key, asset);
|
|
343
|
+
if (asset.kind === "geometry") {
|
|
344
|
+
geometries.set(key, asset);
|
|
345
|
+
if (asset.resource !== undefined) {
|
|
346
|
+
const bytes = new TextEncoder().encode(canonicalJson(asset.primitives));
|
|
347
|
+
if (asset.resource.transport !== "fetch" || asset.resource.mediaType !== "application/json" || asset.resource.bytes !== bytes.byteLength
|
|
348
|
+
|| asset.resource.sha256 !== canonicalJsonSha256(asset.primitives)) {
|
|
349
|
+
throw new RangeError("Prepared geometry resource must bind the exact canonical validated primitives.");
|
|
350
|
+
}
|
|
351
|
+
const previous = resources.get(asset.resource.name);
|
|
352
|
+
if (previous !== undefined && canonicalJson(previous) !== canonicalJson(asset.resource)) throw new RangeError("Geometry resource names must identify exact immutable bytes.");
|
|
353
|
+
resources.set(asset.resource.name, asset.resource);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (asset.kind === "splat") {
|
|
357
|
+
const expected = spatialSpzAllocationBounds(asset.facts.splats, asset.resource.bytes, asset.facts.decompressedBytes);
|
|
358
|
+
if (asset.resource.mediaType !== "application/octet-stream" || asset.resource.bytes < 18 || expected.gpuBytesBound !== asset.facts.gpuBytesBound || expected.hostBytesBound !== asset.facts.hostBytesBound) throw new RangeError("Prepared SPZ allocation or binary resource evidence is invalid.");
|
|
359
|
+
const previous = resources.get(asset.resource.name);
|
|
360
|
+
if (previous !== undefined && canonicalJson(previous) !== canonicalJson(asset.resource)) throw new RangeError("Splat resource names must identify exact immutable bytes.");
|
|
361
|
+
splats.set(key, asset); resources.set(asset.resource.name, asset.resource);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const frameEvidence: Array<{
|
|
365
|
+
readonly sceneSha256: string; readonly stateSha256: string; readonly viewSha256: string; readonly timeUs: number;
|
|
366
|
+
readonly camera: SpatialCamera;
|
|
367
|
+
readonly objects: ReadonlyArray<{ readonly entityId: string; readonly selectionId: number; readonly representation: string; readonly placement: "world" | "view"; readonly assetManifestSha256?: string }>;
|
|
368
|
+
}> = [];
|
|
369
|
+
const frames = request.snapshots.map((snapshot: EvaluatedSpatialScene) => {
|
|
370
|
+
if (snapshot.camera.projection.width !== width || snapshot.camera.projection.height !== height) {
|
|
371
|
+
throw new RangeError("A spatial overlay batch requires identical calibrated output dimensions.");
|
|
372
|
+
}
|
|
373
|
+
const manifests = new Map(snapshot.assets.map(asset => [asset.assetId, asset]));
|
|
374
|
+
const manifestDigests = spatialAssetClosureDigests(snapshot.assets);
|
|
375
|
+
if (manifests.size !== snapshot.assets.length) throw new RangeError("Snapshot asset IDs must be unique.");
|
|
376
|
+
const ids = new Set<string>();
|
|
377
|
+
const selections = new Set<number>();
|
|
378
|
+
const objects: (LoweredMesh | LoweredLight | LoweredSplat)[] = [];
|
|
379
|
+
const evidence: Array<{ entityId: string; selectionId: number; representation: string; placement: "world" | "view"; assetManifestSha256?: string }> = [];
|
|
380
|
+
const bindAsset = (assetId: string, key: string): PreparedSpatialAsset => {
|
|
381
|
+
const manifest = manifests.get(assetId);
|
|
382
|
+
if (manifest === undefined) throw new RangeError(`Snapshot has no manifest for ${assetId}.`);
|
|
383
|
+
const asset = prepared.get(key);
|
|
384
|
+
if (asset === undefined) unsupported(`prepare-${manifest.interpretation.kind}`, `No exact prepared representation for ${key}.`);
|
|
385
|
+
if (asset.assetManifestSha256 !== manifestDigests[assetId]) {
|
|
386
|
+
throw new RangeError(`Prepared asset manifest identity is stale for ${assetId}.`);
|
|
387
|
+
}
|
|
388
|
+
usedPrepared.add(key);
|
|
389
|
+
return asset;
|
|
390
|
+
};
|
|
391
|
+
for (const entry of [...snapshot.entities].sort((a, b) => a.selectionId - b.selectionId)) {
|
|
392
|
+
const entity = entry.entity;
|
|
393
|
+
if (ids.has(entity.entityId) || selections.has(entry.selectionId)) throw new RangeError("Snapshot entity and selection IDs must be unique.");
|
|
394
|
+
ids.add(entity.entityId); selections.add(entry.selectionId);
|
|
395
|
+
invertTransform(entry.worldMatrix);
|
|
396
|
+
if (entity.kind === "splat") {
|
|
397
|
+
if (request.executionProfile !== "three-spark-webgl2-hardware-v1") unsupported("splat-profile", "Splat rendering requires the explicit Three/Spark hardware profile.");
|
|
398
|
+
if (request.mode.kind !== "beauty") unsupported("splat-aov", "Splat object-ID and axial-depth are unsupported; retained colliders are approximate evidence, not pixel truth.");
|
|
399
|
+
if (entity.placement.kind !== "world" || snapshot.camera.projection.kind !== "perspective") unsupported("splat-camera", "The initial Spark profile requires world placement and a perspective camera.");
|
|
400
|
+
const key = `${entity.assetId}:${entity.entityId}:splat`, preparedSplat = bindAsset(entity.assetId, key), manifest = manifests.get(entity.assetId)!;
|
|
401
|
+
if (preparedSplat.kind !== "splat" || manifest.interpretation.kind !== "splat" || manifest.interpretation.format !== "spz" || preparedSplat.resource.sha256 !== manifest.payload.sha256 || preparedSplat.resource.bytes !== manifest.payload.bytes) throw new RangeError("Splat resource does not match the exact source payload.");
|
|
402
|
+
const m = entry.worldMatrix, lengths = [Math.hypot(m[0]!, m[1]!, m[2]!), Math.hypot(m[4]!, m[5]!, m[6]!), Math.hypot(m[8]!, m[9]!, m[10]!)];
|
|
403
|
+
if (Math.max(...lengths) - Math.min(...lengths) > Math.max(...lengths) * 1e-6) unsupported("splat-transform", "The initial Spark profile requires uniform world scale.");
|
|
404
|
+
for (const [left, right] of [[0, 4], [0, 8], [4, 8]] as const) if (Math.abs(m[left]! * m[right]! + m[left + 1]! * m[right + 1]! + m[left + 2]! * m[right + 2]!) > lengths[0]! ** 2 * 1e-6) unsupported("splat-transform", "The initial Spark profile does not accept sheared world transforms.");
|
|
405
|
+
const rotation = manifest.interpretation.sourceUp === "x" ? [0, 0, Math.SQRT1_2, Math.SQRT1_2] as const : manifest.interpretation.sourceUp === "z" ? [-Math.SQRT1_2, 0, 0, Math.SQRT1_2] as const : [0, 0, 0, 1] as const;
|
|
406
|
+
const unit = manifest.interpretation.metersPerUnit;
|
|
407
|
+
if (entry.visible) objects.push({ kind: "splat", entityId: entity.entityId, key, matrix: multiplyTransforms(entry.worldMatrix, composeTransform({ position: [0, 0, 0], rotation, scale: [unit, unit, unit] })) });
|
|
408
|
+
evidence.push({ entityId: entity.entityId, selectionId: entry.selectionId, representation: "spz-static-radiance; authored-wrapper-id; no-depth-or-object-id", placement: "world", assetManifestSha256: preparedSplat.assetManifestSha256 });
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (entity.kind === "group") continue;
|
|
412
|
+
if (entity.kind === "light") {
|
|
413
|
+
if (entity.placement.kind !== "world") unsupported("view-light", "Lights require world placement.");
|
|
414
|
+
if (entry.visible) objects.push({ kind: "light", entityId: entity.entityId, matrix: entry.worldMatrix, light: entity.light, color: entity.color, intensity: entity.intensity });
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
// Capability checks include invisible/off-camera entities, so changing
|
|
418
|
+
// visibility or cameras cannot discover an unplanned loader during render.
|
|
419
|
+
const base = { kind: "mesh" as const, entityId: entity.entityId, selectionId: entry.selectionId, matrix: entry.worldMatrix, placement: entity.placement };
|
|
420
|
+
const unitFit = { uvScale: [1, 1] as const, uvOffset: [0, 0] as const, surfaceScale: [1, 1] as const, flipViewUv: false, doubleSided: true, alphaCutoff: 0 };
|
|
421
|
+
let meshes: LoweredMesh[];
|
|
422
|
+
let representation: string;
|
|
423
|
+
let assetManifestSha256: string | undefined;
|
|
424
|
+
if (entity.kind === "mesh") {
|
|
425
|
+
if (entity.geometry.kind === "asset") {
|
|
426
|
+
const baseKey = `${entity.geometry.assetId}:${entity.entityId}:${entity.geometry.nodeIndex === undefined ? "all" : String(entity.geometry.nodeIndex)}`;
|
|
427
|
+
const timedKey = `${baseKey}:${String(snapshot.timeUs)}`;
|
|
428
|
+
const key = prepared.has(timedKey) ? timedKey : `${baseKey}:static`;
|
|
429
|
+
const asset = bindAsset(entity.geometry.assetId, key);
|
|
430
|
+
if (asset.kind !== "geometry" || manifests.get(asset.assetId)!.interpretation.kind !== "gltf") {
|
|
431
|
+
unsupported("prepared-geometry-kind", "Asset meshes require prepared glTF geometry.");
|
|
432
|
+
}
|
|
433
|
+
if (asset.entityGeometrySha256 !== spatialGeometryContentSha256(entity) || (entity.geometry.clip !== undefined && asset.timeUs !== snapshot.timeUs)) {
|
|
434
|
+
throw new RangeError("Prepared geometry must bind the exact entity geometry, material policy, clip, and sample time.");
|
|
435
|
+
}
|
|
436
|
+
assetManifestSha256 = asset.assetManifestSha256;
|
|
437
|
+
representation = asset.timeUs === null ? "prepared-static-triangle-mesh" : "prepared-evaluated-triangle-mesh";
|
|
438
|
+
const materialMode = entity.geometry.materialMode;
|
|
439
|
+
meshes = asset.primitives.map((primitive, index) => {
|
|
440
|
+
if (primitive.texture !== undefined) bindTexture(primitive.texture);
|
|
441
|
+
const material = primitive.material ?? entity.material;
|
|
442
|
+
if (materialMode === "source" && primitive.material === undefined) throw new RangeError("Source-material geometry requires the exact resolved source material for every primitive.");
|
|
443
|
+
if (materialMode !== "source" && (primitive.material !== undefined || primitive.texture !== undefined || primitive.linearColor !== undefined)) {
|
|
444
|
+
throw new RangeError("Entity-material meshes must not receive source material or texture overrides.");
|
|
445
|
+
}
|
|
446
|
+
assertCoverage(request.mode, material, primitive.texture, primitive.alphaMode);
|
|
447
|
+
return { ...base, ...unitFit, geometry: { kind: "prepared", key, primitive: index }, material,
|
|
448
|
+
doubleSided: primitive.doubleSided ?? true, alphaCutoff: primitive.alphaCutoff ?? 0,
|
|
449
|
+
...(primitive.alphaMode === undefined ? {} : { alphaMode: primitive.alphaMode }),
|
|
450
|
+
...(primitive.linearColor === undefined ? {} : { linearColor: primitive.linearColor }),
|
|
451
|
+
...(primitive.texture === undefined ? {} : { textureName: primitive.texture.resource.name, textureAlpha: primitive.texture.alpha,
|
|
452
|
+
textureOptions: { ...(primitive.texture.flipY === undefined ? {} : { flipY: primitive.texture.flipY }),
|
|
453
|
+
...(primitive.texture.sampler === undefined ? {} : { sampler: primitive.texture.sampler }) } }) };
|
|
454
|
+
});
|
|
455
|
+
} else {
|
|
456
|
+
assertCoverage(request.mode, entity.material, undefined);
|
|
457
|
+
representation = `primitive-${entity.geometry.kind}`;
|
|
458
|
+
meshes = [{ ...base, ...unitFit, geometry: entity.geometry, material: entity.material }];
|
|
459
|
+
}
|
|
460
|
+
} else {
|
|
461
|
+
const assetId = entity.kind === "text" ? entity.fontAssetId : entity.assetId;
|
|
462
|
+
const timedKey = `${assetId}:${entity.entityId}:${String(snapshot.timeUs)}`;
|
|
463
|
+
const key = prepared.has(timedKey) ? timedKey : `${assetId}:${entity.entityId}:static`;
|
|
464
|
+
const asset = bindAsset(assetId, key);
|
|
465
|
+
if (asset.kind !== "raster") unsupported("prepared-raster-kind", "Image, diagram, video, and text entities require prepared raster surfaces.");
|
|
466
|
+
const expected = entity.kind === "text" ? "font" : entity.kind;
|
|
467
|
+
if (manifests.get(assetId)!.interpretation.kind !== expected) throw new RangeError(`Prepared surface asset kind does not match ${entity.kind}.`);
|
|
468
|
+
if (entity.kind === "video" && (asset.timeUs !== snapshot.timeUs || asset.sourceTimeUs === undefined || asset.entityContentSha256 !== spatialVideoRasterContentSha256(entity))) {
|
|
469
|
+
unsupported("video-exact-frame", "Video surfaces require a prepared frame with exact snapshot time and source timestamp.");
|
|
470
|
+
}
|
|
471
|
+
if (entity.kind === "text" && asset.entityContentSha256 !== spatialTextRasterContentSha256(entity)) {
|
|
472
|
+
throw new RangeError("Prepared text raster must bind the exact text, font, size, width, and alignment.");
|
|
473
|
+
}
|
|
474
|
+
bindTexture(asset);
|
|
475
|
+
assetManifestSha256 = asset.assetManifestSha256;
|
|
476
|
+
representation = `prepared-${entity.kind}-raster`;
|
|
477
|
+
const surfaceWidth = entity.width;
|
|
478
|
+
const surfaceHeight = entity.kind === "text" ? surfaceWidth * asset.height / asset.width : entity.height;
|
|
479
|
+
const material: Material = { kind: "unlit", color: entity.kind === "text" ? entity.color : "#ffffff", opacity: entity.kind === "text" ? 1 : entity.opacity };
|
|
480
|
+
assertCoverage(request.mode, material, asset);
|
|
481
|
+
meshes = [{ ...base, geometry: { kind: "plane", width: surfaceWidth, height: surfaceHeight }, material,
|
|
482
|
+
textureName: asset.resource.name, textureAlpha: asset.alpha, ...fitSurface(surfaceWidth, surfaceHeight, asset, entity.kind === "text" ? "stretch" : entity.fit),
|
|
483
|
+
flipViewUv: entity.placement.kind === "view", doubleSided: true, alphaCutoff: 0 }];
|
|
484
|
+
}
|
|
485
|
+
if (!entry.visible || (entity.placement.kind === "view" && entity.placement.cameraId !== snapshot.camera.cameraId)) continue;
|
|
486
|
+
objects.push(...meshes);
|
|
487
|
+
evidence.push({ entityId: entity.entityId, selectionId: entry.selectionId, representation, placement: entity.placement.kind,
|
|
488
|
+
...(assetManifestSha256 === undefined ? {} : { assetManifestSha256 }) });
|
|
489
|
+
}
|
|
490
|
+
const meshes = objects.filter((object): object is LoweredMesh => object.kind === "mesh");
|
|
491
|
+
if (splats.size > 0 && meshes.some(mesh => mesh.placement.kind === "world" && (mesh.material.opacity < 1 || mesh.alphaMode === "BLEND" || mesh.textureAlpha === "straight" && mesh.alphaCutoff === 0))) unsupported("splat-transparency", "The initial world profile supports opaque 3D meshes and view overlays; interleaved transparent mesh/splat sorting is not qualified.");
|
|
492
|
+
const triangles = meshes.reduce((sum, mesh) => sum + geometryTriangleCount(mesh, geometries), 0);
|
|
493
|
+
if (meshes.length > SPATIAL_OVERLAY_LIMITS.drawCallsPerFrame || triangles > SPATIAL_OVERLAY_LIMITS.trianglesPerFrame) {
|
|
494
|
+
throw new RangeError("Spatial overlay exceeds its per-frame draw-call or triangle budget.");
|
|
495
|
+
}
|
|
496
|
+
frameEvidence.push({ sceneSha256: snapshot.sceneSha256, stateSha256: snapshot.stateSha256, viewSha256: snapshot.viewSha256,
|
|
497
|
+
timeUs: snapshot.timeUs, camera: snapshot.camera, objects: evidence });
|
|
498
|
+
return { camera: calibratedCamera(snapshot.camera), objects, ...(request.executionProfile === "three-spark-webgl2-hardware-v1" ? { timeUs: snapshot.timeUs } : {}) };
|
|
499
|
+
});
|
|
500
|
+
if (usedPrepared.size !== prepared.size) throw new RangeError("Prepared assets must be referenced by this exact batch; unused bindings are rejected.");
|
|
501
|
+
const declaredResources = HtmlOverlayDeclaredResourcesSchema.parse([...resources.values()]);
|
|
502
|
+
const decodedTexturePixels = [...textures.values()].reduce((sum, texture) => sum + texture.width * texture.height, 0);
|
|
503
|
+
if (decodedTexturePixels > SPATIAL_OVERLAY_LIMITS.decodedTexturePixels) throw new RangeError("Prepared textures exceed the decoded pixel budget.");
|
|
504
|
+
// Spark's SPZ decoder does not forward the header training flag. Its two
|
|
505
|
+
// renderer-global covariance terms therefore must be bound explicitly. The
|
|
506
|
+
// initial profile admits only non-AA training: add covariance without opacity
|
|
507
|
+
// compensation, rather than applying the AA-trained blur a second time.
|
|
508
|
+
// https://github.com/sparkjsdev/spark/blob/v2.1.0/src/SparkRenderer.ts#L115-L132
|
|
509
|
+
const splatKernel = { antialiased: false, preBlurAmount: 0.3, blurAmount: 0 };
|
|
510
|
+
const payload = {
|
|
511
|
+
mode: request.mode,
|
|
512
|
+
frames,
|
|
513
|
+
geometry: Object.fromEntries([...geometries].filter(([, asset]) => asset.resource === undefined).map(([key, asset]) => [key, asset.primitives])),
|
|
514
|
+
geometryResources: [...geometries].filter(([, asset]) => asset.resource !== undefined).map(([key, asset]) => ({ key, resource: asset.resource!, url: htmlOverlayAssetLocalUrl(asset.resource!) })),
|
|
515
|
+
textures: declaredResources.filter(resource => textures.has(resource.name)).map(resource => ({ ...textures.get(resource.name)!, name: resource.name, url: htmlOverlayAssetLocalUrl(resource) })),
|
|
516
|
+
...(request.executionProfile === "three-spark-webgl2-hardware-v1" ? { splatKernel, splats: [...splats].map(([key, asset]) => ({ key, ...asset, url: htmlOverlayAssetLocalUrl(asset.resource) })) } : {}),
|
|
517
|
+
};
|
|
518
|
+
const totalSplats = [...splats.values()].reduce((sum, asset) => sum + asset.facts.splats, 0);
|
|
519
|
+
// 32 B/pixel conservatively covers the RGBA16F/depth beauty target and the
|
|
520
|
+
// default RGBA8/depth canvas plus renderer framebuffer slack. Driver/process
|
|
521
|
+
// and platform compositor overhead remain outside this allocation accounting.
|
|
522
|
+
if (splats.size > 0 && (totalSplats > SPATIAL_SPLAT_LIMITS.splats || [...splats.values()].reduce((sum, asset) => sum + asset.facts.gpuBytesBound, width * height * 32) > SPATIAL_SPLAT_LIMITS.gpuBytes || [...splats.values()].reduce((sum, asset) => sum + asset.facts.hostBytesBound, 0) > SPATIAL_SPLAT_LIMITS.hostBytes)) throw new RangeError("Prepared world exceeds its aggregate splat/GPU/host allocation bounds.");
|
|
523
|
+
const sparkProfile = request.executionProfile === "three-spark-webgl2-hardware-v1";
|
|
524
|
+
if (sparkProfile && request.mode.kind !== "beauty") unsupported("splat-aov", "The initial Spark profile renders beauty only.");
|
|
525
|
+
const libraries = sparkProfile ? ["@sparkjsdev/spark", "three", "three/addons/postprocessing/Pass.js"] as const : ["three"] as const;
|
|
526
|
+
let html = spatialDocument(escapeEmbeddedJson(payload));
|
|
527
|
+
if (sparkProfile) html = addSpatialSplatRuntime(html.replace(serializeHtmlOverlayImportMap(["three"]), serializeHtmlOverlayImportMap(libraries)));
|
|
528
|
+
const authoring = HtmlOverlayAuthoringInputSchema.parse({
|
|
529
|
+
kind: "slopcamera.html-overlay", schemaVersion: 1, canvas: { width, height, deviceScaleFactor: 1 },
|
|
530
|
+
html, libraries, parameters: {}, resources: declaredResources, seed: 0,
|
|
531
|
+
timing: { fps: 1, durationUs: frames.length * 1_000_000 },
|
|
532
|
+
});
|
|
533
|
+
const metadataValue = {
|
|
534
|
+
kind: "slopcamera.spatial-overlay-batch", schemaVersion: 1,
|
|
535
|
+
renderer: "three-webgl2-snapshot-v1", mode: request.mode,
|
|
536
|
+
...(request.executionProfile === undefined ? {} : { executionProfile: request.executionProfile }),
|
|
537
|
+
...(sparkProfile ? { splatProfile: { adapter: "spark-2.1.0-spz-v2-v3-ext-v1", kernel: splatKernel, lod: false, sorting: "await-explicit-camera-update", readback: "synchronous-exact-MRT-buffer-before-worker-sort", color: "srgb-radiance-to-linear", depth: "unsupported", objectId: "unsupported", collider: "approximate-retained-only", splats: totalSplats } } : {}),
|
|
538
|
+
frameRate: request.frameRate,
|
|
539
|
+
transport: { kind: "frame-index-only", fps: 1, frameCount: frames.length },
|
|
540
|
+
color: request.mode.kind === "beauty"
|
|
541
|
+
? { source: "srgb", working: "linear-srgb", compositing: "linear-half-float-premultiplied", output: "srgb", alpha: "straight-png", toneMapping: "none" }
|
|
542
|
+
: { source: "rgba8-data", working: "rgba8-data", output: "rgba8-data", alpha: "binary-validity", toneMapping: "none" },
|
|
543
|
+
rasterization: { antialias: false, sampling: "single-pixel-center", surfaces: "authored-primitives-double-sided; prepared-mesh-explicit-sidedness", transparentOrdering: "three-object-sort" },
|
|
544
|
+
selectionEncoding: { channels: "RGB-big-endian-uint24", noHit: [0, 0, 0, 0] },
|
|
545
|
+
depthEncoding: {
|
|
546
|
+
channels: "RGB-big-endian-uint24", noHit: [0, 0, 0, 0], validAlpha: 255,
|
|
547
|
+
formula: "near + (code - 1) / 16777214 * (far - near)", units: "positive-camera-axial-meters",
|
|
548
|
+
viewSurfaces: "mask-to-no-hit", quantizationSteps: 16_777_214,
|
|
549
|
+
toleranceMeters: frameEvidence.map(frame => Math.max(2 * (frame.camera.projection.far - frame.camera.projection.near) / 16_777_214, frame.camera.projection.far * 2 ** -20, 1e-6)),
|
|
550
|
+
},
|
|
551
|
+
cameraConvention: "camera-to-world; column-major; XYZW; local-minus-Z; top-left-image-boundary; pixel-centers-i+0.5,j+0.5",
|
|
552
|
+
viewConvention: "top-left-image-boundary; X-right,Y-down; normalized-XY-scaled-by-output-dimensions; order-after-world",
|
|
553
|
+
preparation: request.preparedAssets.map(asset => ({ key: preparedKey(asset), assetManifestSha256: asset.assetManifestSha256,
|
|
554
|
+
...(asset.kind === "raster" && asset.sourceTimeUs !== undefined ? { sourceTimeUs: asset.sourceTimeUs } : {}),
|
|
555
|
+
...(asset.kind === "raster" && asset.sourceFrameIndex !== undefined ? { sourceFrameIndex: asset.sourceFrameIndex } : {}),
|
|
556
|
+
...(asset.kind === "raster" && asset.sourcePresentationTimeUs !== undefined ? { sourcePresentationTimeUs: asset.sourcePresentationTimeUs } : {}),
|
|
557
|
+
...(asset.kind === "raster" && asset.sourcePts !== undefined ? { sourcePts: asset.sourcePts } : {}),
|
|
558
|
+
...(asset.kind === "raster" && asset.sourceTimeBase !== undefined ? { sourceTimeBase: asset.sourceTimeBase } : {}),
|
|
559
|
+
...(asset.kind === "raster" && asset.sourceTimestamp !== undefined ? { sourceTimestamp: asset.sourceTimestamp } : {}),
|
|
560
|
+
...(asset.kind === "raster" && asset.sourceExactTimeUs !== undefined ? { sourceExactTimeUs: asset.sourceExactTimeUs } : {}),
|
|
561
|
+
})),
|
|
562
|
+
costs: { requestBytes: captured.bytes, htmlBytes: new TextEncoder().encode(html).byteLength,
|
|
563
|
+
decodedTexturePixels, resourceBytes: declaredResources.reduce((sum, resource) => sum + resource.bytes, 0),
|
|
564
|
+
note: sparkProfile ? "One isolated browser per batch; retained SPZ sources load once, each exact camera sample awaits full-resolution sort. Admission bounds are not measured GPU performance." : "Each batch uses one isolated browser render; scenes and frame-local GPU objects are rebuilt for every selected snapshot. These are admission counts, not measured GPU performance." },
|
|
565
|
+
frames: frameEvidence,
|
|
566
|
+
};
|
|
567
|
+
const metadata = createBoundedJsonSnapshot(metadataValue, SPATIAL_OVERLAY_LIMITS.requestBytes, "Spatial overlay metadata");
|
|
568
|
+
return Object.freeze({ authoring: Object.freeze(authoring), metadata: metadata.value as unknown as DeepReadonly<typeof metadataValue>, metadataSha256: metadata.sha256 });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function spatialDocument(payload: string): string {
|
|
572
|
+
return `<!doctype html>
|
|
573
|
+
<html><head><meta charset="utf-8"><style>html,body{margin:0;width:100%;height:100%;overflow:hidden;background:transparent}canvas{display:block;width:100%;height:100%}</style>
|
|
574
|
+
<script type="importmap">${serializeHtmlOverlayImportMap(["three"])}</script></head><body><canvas></canvas>
|
|
575
|
+
<script type="module">
|
|
576
|
+
import * as THREE from "three";
|
|
577
|
+
const input=${payload};
|
|
578
|
+
const canvas=document.querySelector("canvas");
|
|
579
|
+
const renderer=new THREE.WebGLRenderer({canvas,alpha:true,antialias:false,premultipliedAlpha:true,preserveDrawingBuffer:true});
|
|
580
|
+
renderer.setPixelRatio(1);renderer.setSize(SlopcameraOverlay.width,SlopcameraOverlay.height,false);
|
|
581
|
+
renderer.setClearColor(0x000000,0);renderer.outputColorSpace=THREE.SRGBColorSpace;
|
|
582
|
+
renderer.toneMapping=THREE.NoToneMapping;renderer.autoClear=false;
|
|
583
|
+
let beautyTarget=null,outputScene=null,outputCamera=null,outputGeometry=null,outputMaterial=null;
|
|
584
|
+
if(input.mode.kind==="beauty"){
|
|
585
|
+
if(!renderer.extensions.has("EXT_color_buffer_float"))throw new Error("Spatial SDR beauty requires renderable half-float linear color for correct alpha compositing.");
|
|
586
|
+
beautyTarget=new THREE.WebGLRenderTarget(SlopcameraOverlay.width,SlopcameraOverlay.height,{type:THREE.HalfFloatType,format:THREE.RGBAFormat,colorSpace:THREE.LinearSRGBColorSpace,minFilter:THREE.NearestFilter,magFilter:THREE.NearestFilter,depthBuffer:true,stencilBuffer:false});
|
|
587
|
+
outputGeometry=new THREE.PlaneGeometry(2,2);
|
|
588
|
+
outputMaterial=new THREE.RawShaderMaterial({depthTest:false,depthWrite:false,blending:THREE.NoBlending,transparent:false,toneMapped:false,dithering:false,
|
|
589
|
+
uniforms:{linearPremultiplied:{value:beautyTarget.texture}},
|
|
590
|
+
vertexShader:"precision highp float;attribute vec3 position;attribute vec2 uv;varying vec2 sampleUv;void main(){sampleUv=uv;gl_Position=vec4(position.xy,0.0,1.0);}",
|
|
591
|
+
fragmentShader:"precision highp float;uniform sampler2D linearPremultiplied;varying vec2 sampleUv;vec3 toSrgb(vec3 v){return mix(12.92*v,1.055*pow(max(v,vec3(0.0)),vec3(1.0/2.4))-0.055,step(vec3(0.0031308),v));}void main(){vec4 c=texture2D(linearPremultiplied,sampleUv);if(c.a<=0.0){gl_FragColor=vec4(0.0);return;}gl_FragColor=vec4(toSrgb(c.rgb/c.a)*c.a,c.a);}",
|
|
592
|
+
});
|
|
593
|
+
outputScene=new THREE.Scene();const outputMesh=new THREE.Mesh(outputGeometry,outputMaterial);outputMesh.frustumCulled=false;outputScene.add(outputMesh);outputCamera=new THREE.Camera();
|
|
594
|
+
}
|
|
595
|
+
const textures=new Map();let disposed=false;let contextFailure=null;
|
|
596
|
+
const loseContext=event=>{event.preventDefault();contextFailure=new Error("Spatial WebGL context was lost.");};
|
|
597
|
+
canvas.addEventListener("webglcontextlost",loseContext);
|
|
598
|
+
const initialization=(async()=>{
|
|
599
|
+
const loadedGeometry=new Map();
|
|
600
|
+
for(const item of input.geometryResources){
|
|
601
|
+
let primitives=loadedGeometry.get(item.resource.sha256);
|
|
602
|
+
if(primitives===undefined){
|
|
603
|
+
const response=await fetch(item.url);if(!response.ok)throw new Error("Prepared geometry resource is unavailable.");
|
|
604
|
+
const bytes=await response.arrayBuffer();
|
|
605
|
+
if(bytes.byteLength!==item.resource.bytes)throw new Error("Prepared geometry resource length changed.");
|
|
606
|
+
const hash=new Uint8Array(await crypto.subtle.digest("SHA-256",bytes));
|
|
607
|
+
const sha256=Array.from(hash,value=>value.toString(16).padStart(2,"0")).join("");
|
|
608
|
+
if(sha256!==item.resource.sha256)throw new Error("Prepared geometry resource digest changed.");
|
|
609
|
+
primitives=JSON.parse(new TextDecoder("utf-8",{fatal:true}).decode(bytes));
|
|
610
|
+
loadedGeometry.set(sha256,primitives);
|
|
611
|
+
}
|
|
612
|
+
if(disposed)throw new Error("Spatial renderer was disposed during geometry preparation.");
|
|
613
|
+
input.geometry[item.key]=primitives;
|
|
614
|
+
}
|
|
615
|
+
for(const item of input.textures){
|
|
616
|
+
const texture=await new THREE.TextureLoader().loadAsync(item.url);
|
|
617
|
+
if(disposed){texture.dispose();throw new Error("Spatial renderer was disposed during texture preparation.");}
|
|
618
|
+
if(texture.image.width!==item.width||texture.image.height!==item.height){texture.dispose();throw new Error("Prepared raster dimensions changed.");}
|
|
619
|
+
texture.colorSpace=THREE.SRGBColorSpace;texture.generateMipmaps=false;
|
|
620
|
+
texture.minFilter=THREE.LinearFilter;texture.magFilter=THREE.LinearFilter;
|
|
621
|
+
texture.wrapS=THREE.ClampToEdgeWrapping;texture.wrapT=THREE.ClampToEdgeWrapping;
|
|
622
|
+
textures.set(item.name,texture);
|
|
623
|
+
}
|
|
624
|
+
})().catch(error=>{for(const texture of textures.values())texture.dispose();textures.clear();throw error;});
|
|
625
|
+
SlopcameraOverlay.ready(initialization);
|
|
626
|
+
const vertexShader=\`precision highp float;
|
|
627
|
+
uniform mat4 projectionMatrix;uniform mat4 modelViewMatrix;
|
|
628
|
+
attribute vec3 position;attribute vec2 uv;
|
|
629
|
+
varying vec2 surfaceUv;varying float axialDepth;
|
|
630
|
+
void main(){vec4 p=modelViewMatrix*vec4(position,1.0);surfaceUv=uv;axialDepth=-p.z;gl_Position=projectionMatrix*p;}\`;
|
|
631
|
+
const fragmentShader=\`precision highp float;
|
|
632
|
+
uniform sampler2D surfaceMap;uniform bool hasMap;uniform bool textureOpaque;uniform vec2 uvScale;uniform vec2 uvOffset;uniform bool flipViewUv;
|
|
633
|
+
uniform float opacity;uniform float threshold;uniform bool isMask;uniform float maskCutoff;uniform vec3 selection;uniform float nearClip;uniform float farClip;uniform int mode;
|
|
634
|
+
varying vec2 surfaceUv;varying float axialDepth;
|
|
635
|
+
void main(){vec2 uv=surfaceUv;if(flipViewUv)uv.y=1.0-uv.y;uv=uv*uvScale+uvOffset;
|
|
636
|
+
float alpha=opacity*(hasMap&&!textureOpaque?texture2D(surfaceMap,uv).a:1.0);if(isMask){if(alpha<maskCutoff)discard;alpha=1.0;}if(alpha<threshold)discard;
|
|
637
|
+
if(mode==0){gl_FragColor=vec4(selection,1.0);return;}if(mode==2){gl_FragColor=vec4(0.0);return;}
|
|
638
|
+
float code=1.0+floor(clamp((axialDepth-nearClip)/(farClip-nearClip),0.0,1.0)*16777214.0+0.5);
|
|
639
|
+
float high=floor(code/65536.0);float middle=floor((code-high*65536.0)/256.0);float low=code-high*65536.0-middle*256.0;
|
|
640
|
+
gl_FragColor=vec4(vec3(high,middle,low)/255.0,1.0);}\`;
|
|
641
|
+
function makeGeometry(object,track){
|
|
642
|
+
const g=object.geometry;let geometry;
|
|
643
|
+
if(g.kind==="box")geometry=new THREE.BoxGeometry(...g.size);
|
|
644
|
+
else if(g.kind==="sphere")geometry=new THREE.SphereGeometry(g.radius,32,16);
|
|
645
|
+
else if(g.kind==="plane")geometry=new THREE.PlaneGeometry(g.width*object.surfaceScale[0],g.height*object.surfaceScale[1]);
|
|
646
|
+
else if(g.kind==="cylinder")geometry=new THREE.CylinderGeometry(g.radius,g.radius,g.height,32);
|
|
647
|
+
else{const p=input.geometry[g.key][g.primitive];geometry=new THREE.BufferGeometry();
|
|
648
|
+
geometry.setAttribute("position",new THREE.Float32BufferAttribute(p.positions,3));
|
|
649
|
+
if(p.normals)geometry.setAttribute("normal",new THREE.Float32BufferAttribute(p.normals,3));
|
|
650
|
+
if(p.uvs)geometry.setAttribute("uv",new THREE.Float32BufferAttribute(p.uvs,2));
|
|
651
|
+
if(p.indices)geometry.setIndex(p.indices);if(!p.normals)geometry.computeVertexNormals();}
|
|
652
|
+
return track(geometry);
|
|
653
|
+
}
|
|
654
|
+
function makeMaterial(object,frame,track){
|
|
655
|
+
const m=object.material;const originalMap=object.textureName===undefined?null:textures.get(object.textureName);
|
|
656
|
+
const map=originalMap===null?null:track(originalMap.clone());
|
|
657
|
+
if(map){
|
|
658
|
+
const options=object.textureOptions;
|
|
659
|
+
if(options?.flipY!==undefined)map.flipY=options.flipY;
|
|
660
|
+
if(options?.sampler){const s=options.sampler;
|
|
661
|
+
const wraps={33071:THREE.ClampToEdgeWrapping,33648:THREE.MirroredRepeatWrapping,10497:THREE.RepeatWrapping};
|
|
662
|
+
const filters={9728:THREE.NearestFilter,9729:THREE.LinearFilter,9984:THREE.NearestMipmapNearestFilter,9985:THREE.LinearMipmapNearestFilter,9986:THREE.NearestMipmapLinearFilter,9987:THREE.LinearMipmapLinearFilter};
|
|
663
|
+
map.wrapS=wraps[s.wrapS];map.wrapT=wraps[s.wrapT];map.magFilter=filters[s.magFilter??9729];map.minFilter=filters[s.minFilter??9987];map.generateMipmaps=(s.minFilter??9987)>=9984;
|
|
664
|
+
}map.needsUpdate=true;
|
|
665
|
+
}
|
|
666
|
+
const view=object.placement.kind==="view";
|
|
667
|
+
if(input.mode.kind!=="beauty"){
|
|
668
|
+
const id=object.selectionId;
|
|
669
|
+
return track(new THREE.RawShaderMaterial({vertexShader,fragmentShader,side:object.doubleSided?THREE.DoubleSide:THREE.FrontSide,
|
|
670
|
+
transparent:false,blending:THREE.NoBlending,depthWrite:!view,depthTest:!view,toneMapped:false,dithering:false,
|
|
671
|
+
uniforms:{surfaceMap:{value:map},hasMap:{value:map!==null},textureOpaque:{value:object.textureAlpha==="opaque"},uvScale:{value:new THREE.Vector2(...object.uvScale)},
|
|
672
|
+
uvOffset:{value:new THREE.Vector2(...object.uvOffset)},flipViewUv:{value:object.flipViewUv},opacity:{value:m.opacity},
|
|
673
|
+
threshold:{value:input.mode.coverage.kind==="opaque"?1:input.mode.coverage.threshold},isMask:{value:object.alphaMode==="MASK"},maskCutoff:{value:object.alphaCutoff},
|
|
674
|
+
selection:{value:new THREE.Vector3(Math.floor(id/65536)/255,(Math.floor(id/256)%256)/255,(id%256)/255)},
|
|
675
|
+
nearClip:{value:frame.camera.near},farClip:{value:frame.camera.far},mode:{value:input.mode.kind==="object-id"?0:view?2:1}}}));
|
|
676
|
+
}
|
|
677
|
+
if(map){map.repeat.fromArray(object.uvScale);map.offset.fromArray(object.uvOffset);
|
|
678
|
+
if(object.flipViewUv){map.repeat.y*=-1;map.offset.y+=object.uvScale[1];}map.needsUpdate=true;}
|
|
679
|
+
const transparent=object.alphaMode===undefined?object.alphaCutoff===0&&(m.opacity<1||object.textureAlpha==="straight"):object.alphaMode==="BLEND";
|
|
680
|
+
const options={color:m.color,opacity:m.opacity,transparent,map,
|
|
681
|
+
side:object.doubleSided?THREE.DoubleSide:THREE.FrontSide,alphaTest:object.alphaCutoff,depthTest:!view,depthWrite:!view&&!transparent,toneMapped:false};
|
|
682
|
+
const material=track(m.kind==="unlit"?new THREE.MeshBasicMaterial(options):new THREE.MeshStandardMaterial({...options,roughness:m.roughness,metalness:m.metalness}));
|
|
683
|
+
if(object.linearColor)material.color.setRGB(...object.linearColor,THREE.LinearSRGBColorSpace);
|
|
684
|
+
return material;
|
|
685
|
+
}
|
|
686
|
+
function makeCamera(data){
|
|
687
|
+
const camera=data.kind==="perspective"?new THREE.PerspectiveCamera():new THREE.OrthographicCamera();camera.matrixAutoUpdate=false;camera.matrix.fromArray(data.cameraToWorld);
|
|
688
|
+
camera.projectionMatrix.fromArray(data.projection);camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
689
|
+
camera.updateMatrixWorld(true);return camera;
|
|
690
|
+
}
|
|
691
|
+
SlopcameraOverlay.onFrame(({frame:index})=>{
|
|
692
|
+
if(disposed)throw new Error("Spatial renderer is disposed.");if(contextFailure)throw contextFailure;
|
|
693
|
+
const frame=input.frames[index];if(!frame)throw new Error("Spatial frame index is outside the immutable batch.");
|
|
694
|
+
const disposable=[];const track=value=>(disposable.push(value),value);
|
|
695
|
+
try{
|
|
696
|
+
const world=new THREE.Scene();const view=new THREE.Scene();const camera=makeCamera(frame.camera);
|
|
697
|
+
const viewCamera=new THREE.OrthographicCamera(0,SlopcameraOverlay.width,0,SlopcameraOverlay.height,0.01,2000002);viewCamera.position.z=1000001;viewCamera.updateMatrixWorld(true);
|
|
698
|
+
for(const object of frame.objects){
|
|
699
|
+
if(object.kind==="light"){
|
|
700
|
+
const light=object.light==="ambient"?new THREE.AmbientLight(object.color,object.intensity):object.light==="point"?new THREE.PointLight(object.color,object.intensity):new THREE.DirectionalLight(object.color,object.intensity);
|
|
701
|
+
light.matrixAutoUpdate=false;light.matrix.fromArray(object.matrix);world.add(light);
|
|
702
|
+
if(object.light==="directional"){const target=new THREE.Object3D();target.position.set(0,0,-1).applyMatrix4(light.matrix);world.add(target);light.target=target;}
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
const mesh=new THREE.Mesh(makeGeometry(object,track),makeMaterial(object,frame,track));mesh.name=object.entityId;
|
|
706
|
+
mesh.matrixAutoUpdate=false;mesh.matrix.fromArray(object.matrix);
|
|
707
|
+
if(object.geometry.kind==="prepared")mesh.matrix.multiply(new THREE.Matrix4().fromArray(input.geometry[object.geometry.key][object.geometry.primitive].matrix));
|
|
708
|
+
if(object.placement.kind==="view"){
|
|
709
|
+
if(object.placement.units==="normalized")mesh.matrix.premultiply(new THREE.Matrix4().makeScale(SlopcameraOverlay.width,SlopcameraOverlay.height,1));
|
|
710
|
+
mesh.renderOrder=object.placement.order;mesh.frustumCulled=false;view.add(mesh);
|
|
711
|
+
}else world.add(mesh);
|
|
712
|
+
}
|
|
713
|
+
renderer.setRenderTarget(beautyTarget);renderer.clear(true,true,true);renderer.render(world,camera);renderer.clearDepth();renderer.render(view,viewCamera);
|
|
714
|
+
if(beautyTarget){renderer.setRenderTarget(null);renderer.clear(true,true,true);renderer.render(outputScene,outputCamera);}
|
|
715
|
+
if(contextFailure)throw contextFailure;
|
|
716
|
+
}finally{for(const resource of disposable)resource.dispose();}
|
|
717
|
+
});
|
|
718
|
+
addEventListener("pagehide",()=>{disposed=true;canvas.removeEventListener("webglcontextlost",loseContext);for(const texture of textures.values())texture.dispose();textures.clear();beautyTarget?.dispose();outputGeometry?.dispose();outputMaterial?.dispose();renderer.dispose();renderer.forceContextLoss();});
|
|
719
|
+
</script></body></html>`;
|
|
720
|
+
}
|