@vibecook/ice 0.10.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -50
- package/dist/chunk-BA7XJC27.js +2429 -0
- package/dist/chunk-BA7XJC27.js.map +1 -0
- package/dist/chunk-BKV4ZEU3.js +1 -0
- package/dist/chunk-BKV4ZEU3.js.map +1 -0
- package/dist/{chunk-5FQS7V2C.js → chunk-DYK6KNLG.js} +13303 -7485
- package/dist/chunk-DYK6KNLG.js.map +1 -0
- package/dist/chunk-ENIFIUEP.js +155 -0
- package/dist/chunk-ENIFIUEP.js.map +1 -0
- package/dist/chunk-IBWWLA6D.js +634 -0
- package/dist/chunk-IBWWLA6D.js.map +1 -0
- package/dist/{chunk-OR5FNVUE.js → chunk-JS6PG6YD.js} +398 -167
- package/dist/chunk-JS6PG6YD.js.map +1 -0
- package/dist/chunk-QXOCF56A.js +148 -0
- package/dist/chunk-QXOCF56A.js.map +1 -0
- package/dist/chunk-ZPUPBULB.js +7399 -0
- package/dist/chunk-ZPUPBULB.js.map +1 -0
- package/dist/desk-engine.js +36 -0
- package/dist/desk-engine.js.map +1 -0
- package/dist/desk-kit.js +244 -0
- package/dist/desk-kit.js.map +1 -0
- package/dist/desk-objects.js +12728 -0
- package/dist/desk-objects.js.map +1 -0
- package/dist/desk.js +469 -0
- package/dist/desk.js.map +1 -0
- package/dist/devtools.js +233 -189
- package/dist/devtools.js.map +1 -1
- package/dist/dom.js +7 -19
- package/dist/index.js +232 -20
- package/dist/kernel.js +53 -25
- package/dist/react.js +1045 -38
- package/dist/react.js.map +1 -1
- package/dist/types/packages/core/src/behavior/define-behavior.d.ts +11 -0
- package/dist/types/packages/core/src/behavior/runtime.d.ts +4 -0
- package/dist/types/packages/core/src/canvas/define-canvas-type.d.ts +86 -0
- package/dist/types/packages/core/src/canvas/define-container.d.ts +17 -0
- package/dist/types/packages/core/src/canvas/diagnostics.d.ts +27 -0
- package/dist/types/packages/core/src/canvas/engine-catalog.d.ts +70 -0
- package/dist/types/packages/core/src/canvas/extensions.d.ts +125 -0
- package/dist/types/packages/core/src/canvas/frame-behavior-host.d.ts +16 -0
- package/dist/types/packages/core/src/canvas/frame-preview.d.ts +79 -0
- package/dist/types/packages/core/src/canvas/frame-projection.d.ts +38 -0
- package/dist/types/packages/core/src/canvas/frame-view.d.ts +36 -0
- package/dist/types/packages/core/src/canvas/migrate.d.ts +14 -0
- package/dist/types/packages/core/src/canvas/placement.d.ts +30 -0
- package/dist/types/packages/core/src/canvas/presentation-transition.d.ts +100 -0
- package/dist/types/packages/core/src/canvas/runtime-extension-host.d.ts +16 -0
- package/dist/types/packages/core/src/canvas/session.d.ts +47 -0
- package/dist/types/packages/core/src/catalog/camera-derived.d.ts +16 -27
- package/dist/types/packages/core/src/catalog/desk.d.ts +326 -0
- package/dist/types/packages/core/src/catalog/gesture.d.ts +51 -0
- package/dist/types/packages/core/src/catalog/graph.d.ts +2 -2
- package/dist/types/packages/core/src/catalog/index.d.ts +2 -0
- package/dist/types/packages/core/src/catalog/pointer.d.ts +42 -0
- package/dist/types/packages/core/src/catalog/selection-presence.d.ts +7 -0
- package/dist/types/packages/core/src/catalog/settings-resources.d.ts +28 -2
- package/dist/types/packages/core/src/catalog/surface.d.ts +1 -0
- package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +18 -2
- package/dist/types/packages/core/src/doc/doc-kit.d.ts +21 -2
- package/dist/types/packages/core/src/doc/envelope.d.ts +5 -2
- package/dist/types/packages/core/src/doc/schema-migrate.d.ts +1 -1
- package/dist/types/packages/core/src/doc/version-gate.d.ts +21 -3
- package/dist/types/packages/core/src/engine/commit-sink.d.ts +10 -1
- package/dist/types/packages/core/src/engine/engine.d.ts +6 -0
- package/dist/types/packages/core/src/engine/frame-control.d.ts +74 -0
- package/dist/types/packages/core/src/engine/guests.d.ts +4 -1
- package/dist/types/packages/core/src/engine/world-doors.d.ts +12 -0
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +146 -13
- package/dist/types/packages/core/src/index.d.ts +34 -13
- package/dist/types/packages/core/src/input/queue.d.ts +3 -1
- package/dist/types/packages/core/src/interaction/install.d.ts +30 -0
- package/dist/types/packages/core/src/interaction/wakes.d.ts +24 -0
- package/dist/types/packages/core/src/nav/nav-geometry.d.ts +100 -0
- package/dist/types/packages/core/src/nav/nested-canvas.d.ts +47 -4
- package/dist/types/packages/core/src/ops/cascade.d.ts +2 -1
- package/dist/types/packages/core/src/ops/claims.d.ts +6 -0
- package/dist/types/packages/core/src/ops/insert.d.ts +4 -0
- package/dist/types/packages/core/src/ops/point-pick.d.ts +4 -1
- package/dist/types/packages/core/src/ops/sibling-order.d.ts +14 -4
- package/dist/types/packages/core/src/ops/tray.d.ts +48 -0
- package/dist/types/packages/core/src/presence/presence-kit.d.ts +5 -0
- package/dist/types/packages/core/src/schema/meta.d.ts +10 -0
- package/dist/types/packages/core/src/schema/prefab.d.ts +8 -0
- package/dist/types/packages/core/src/settings/defaults.d.ts +25 -6
- package/dist/types/packages/core/src/surface/gpu-device.d.ts +55 -0
- package/dist/types/packages/core/src/systems/camera-sim.d.ts +29 -0
- package/dist/types/packages/core/src/systems/chrome.d.ts +15 -23
- package/dist/types/packages/core/src/systems/drag-mods.d.ts +34 -0
- package/dist/types/packages/core/src/systems/held.d.ts +71 -0
- package/dist/types/packages/core/src/systems/insert-ghost.d.ts +2 -0
- package/dist/types/packages/core/src/systems/l0-input.d.ts +14 -0
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +37 -1
- package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +18 -4
- package/dist/types/packages/core/src/systems/l3-behave.d.ts +4 -1
- package/dist/types/packages/core/src/systems/l3-claim.d.ts +7 -0
- package/dist/types/packages/core/src/systems/l3-connect.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-drop.d.ts +30 -1
- package/dist/types/packages/core/src/systems/l3-marquee.d.ts +2 -1
- package/dist/types/packages/core/src/systems/l3-ports.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l4-cursor.d.ts +5 -0
- package/dist/types/packages/core/src/systems/nav-flight.d.ts +85 -3
- package/dist/types/packages/core/src/systems/nav-tap.d.ts +44 -0
- package/dist/types/packages/core/src/systems/press-wheel.d.ts +11 -0
- package/dist/types/packages/core/src/systems/tray.d.ts +108 -0
- package/dist/types/packages/core/src/systems/zoom-through.d.ts +30 -0
- package/dist/types/packages/core/src/tools/define-tool.d.ts +10 -0
- package/dist/types/packages/core/src/widget/define-widget.d.ts +166 -63
- package/dist/types/packages/core/src/widget/equip.d.ts +14 -2
- package/dist/types/packages/core/src/widget/held-tools.d.ts +83 -0
- package/dist/types/packages/core/src/widget/menu-actions.d.ts +44 -0
- package/dist/types/packages/core/src/widget/mount-store.d.ts +21 -46
- package/dist/types/packages/core/src/widget/spawn.d.ts +1 -1
- package/dist/types/packages/desk/src/assets/blue-noise.gen.d.ts +10 -0
- package/dist/types/packages/desk/src/card/card.d.ts +103 -0
- package/dist/types/packages/desk/src/compose/ambient.d.ts +59 -0
- package/dist/types/packages/desk/src/compose/builder.d.ts +285 -0
- package/dist/types/packages/desk/src/compose/children.d.ts +3 -0
- package/dist/types/packages/desk/src/compose/index.d.ts +14 -0
- package/dist/types/packages/desk/src/compose/marks.d.ts +126 -0
- package/dist/types/packages/desk/src/compose/pick.d.ts +5 -0
- package/dist/types/packages/desk/src/compose/reflector.d.ts +111 -0
- package/dist/types/packages/desk/src/compose/tap.d.ts +5 -0
- package/dist/types/packages/desk/src/docs.d.ts +23 -0
- package/dist/types/packages/desk/src/engine/budget.d.ts +38 -0
- package/dist/types/packages/desk/src/engine/device.d.ts +54 -0
- package/dist/types/packages/desk/src/engine/index.d.ts +5 -0
- package/dist/types/packages/desk/src/engine/pipeline.d.ts +36 -0
- package/dist/types/packages/desk/src/engine/rasters.d.ts +66 -0
- package/dist/types/packages/desk/src/engine/records.d.ts +56 -0
- package/dist/types/packages/desk/src/engine/shader.d.ts +22 -0
- package/dist/types/packages/desk/src/engine/struct.d.ts +39 -0
- package/dist/types/packages/desk/src/engine/target.d.ts +26 -0
- package/dist/types/packages/desk/src/gpu-ablation.d.ts +61 -0
- package/dist/types/packages/desk/src/gpu-memory.d.ts +54 -0
- package/dist/types/packages/desk/src/gpu-profiler.d.ts +141 -0
- package/dist/types/packages/desk/src/gpu-wrap.d.ts +8 -0
- package/dist/types/packages/desk/src/ground.d.ts +388 -0
- package/dist/types/packages/desk/src/hold/focus.d.ts +53 -0
- package/dist/types/packages/desk/src/hold/pose.d.ts +132 -0
- package/dist/types/packages/desk/src/hold/shaders.d.ts +9 -0
- package/dist/types/packages/desk/src/host/editor.d.ts +18 -0
- package/dist/types/packages/desk/src/host/index.d.ts +9 -0
- package/dist/types/packages/desk/src/host/ink.d.ts +18 -0
- package/dist/types/packages/desk/src/host/layer.d.ts +430 -0
- package/dist/types/packages/desk/src/host/picture.d.ts +2 -0
- package/dist/types/packages/desk/src/host/surface.d.ts +5 -0
- package/dist/types/packages/desk/src/index.d.ts +13 -0
- package/dist/types/packages/desk/src/kind.d.ts +187 -0
- package/dist/types/packages/desk/src/kinds/world.d.ts +429 -0
- package/dist/types/packages/desk/src/kit/arrays.d.ts +88 -0
- package/dist/types/packages/desk/src/kit/blobs.d.ts +40 -0
- package/dist/types/packages/desk/src/kit/book.d.ts +2 -0
- package/dist/types/packages/desk/src/kit/editor.d.ts +103 -0
- package/dist/types/packages/desk/src/kit/eye.d.ts +37 -0
- package/dist/types/packages/desk/src/kit/hold.d.ts +1 -0
- package/dist/types/packages/desk/src/kit/index.d.ts +26 -0
- package/dist/types/packages/desk/src/kit/inside.d.ts +116 -0
- package/dist/types/packages/desk/src/kit/layer.d.ts +159 -0
- package/dist/types/packages/desk/src/kit/light.d.ts +3 -0
- package/dist/types/packages/desk/src/kit/mesh.d.ts +29 -0
- package/dist/types/packages/desk/src/kit/mips.d.ts +2 -0
- package/dist/types/packages/desk/src/kit/nav.d.ts +2 -0
- package/dist/types/packages/desk/src/kit/paper-tex.d.ts +5 -0
- package/dist/types/packages/desk/src/kit/physics.d.ts +44 -0
- package/dist/types/packages/desk/src/kit/place.d.ts +44 -0
- package/dist/types/packages/desk/src/kit/print.d.ts +30 -0
- package/dist/types/packages/desk/src/kit/raster.d.ts +34 -0
- package/dist/types/packages/desk/src/kit/sdf.d.ts +1 -0
- package/dist/types/packages/desk/src/kit/seeds.d.ts +11 -0
- package/dist/types/packages/desk/src/kit/services.d.ts +34 -0
- package/dist/types/packages/desk/src/kit/springs.d.ts +12 -0
- package/dist/types/packages/desk/src/kit/strokes.d.ts +93 -0
- package/dist/types/packages/desk/src/kit/text.d.ts +142 -0
- package/dist/types/packages/desk/src/kit/uniform.d.ts +8 -0
- package/dist/types/packages/desk/src/kit/view.d.ts +39 -0
- package/dist/types/packages/desk/src/kit/wgsl.d.ts +15 -0
- package/dist/types/packages/desk/src/lattice/line.d.ts +15 -0
- package/dist/types/packages/desk/src/lattice/lod.d.ts +52 -0
- package/dist/types/packages/desk/src/lattice/ruler.d.ts +88 -0
- package/dist/types/packages/desk/src/marks/assemble.d.ts +65 -0
- package/dist/types/packages/desk/src/marks/ease.d.ts +4 -0
- package/dist/types/packages/desk/src/marks/laser.d.ts +41 -0
- package/dist/types/packages/desk/src/marks/layout.d.ts +167 -0
- package/dist/types/packages/desk/src/marks/pass.d.ts +59 -0
- package/dist/types/packages/desk/src/marks/shaders.d.ts +12 -0
- package/dist/types/packages/desk/src/mat/grid.d.ts +50 -0
- package/dist/types/packages/desk/src/mat/lamp.d.ts +14 -0
- package/dist/types/packages/desk/src/mat/layout.d.ts +144 -0
- package/dist/types/packages/desk/src/mat/mat-pass.d.ts +83 -0
- package/dist/types/packages/desk/src/mat/night.d.ts +95 -0
- package/dist/types/packages/desk/src/mat/projector.d.ts +39 -0
- package/dist/types/packages/desk/src/mat/shaders.d.ts +17 -0
- package/dist/types/packages/desk/src/mat/tilt.d.ts +14 -0
- package/dist/types/packages/desk/src/nav/flight.d.ts +186 -0
- package/dist/types/packages/desk/src/nav/portal.d.ts +95 -0
- package/dist/types/packages/desk/src/object.d.ts +46 -0
- package/dist/types/packages/desk/src/pass-instrument.d.ts +89 -0
- package/dist/types/packages/desk/src/shaders.d.ts +12 -0
- package/dist/types/packages/desk/src/shaders.gen.d.ts +16 -0
- package/dist/types/packages/desk/src/submit-instrument.d.ts +36 -0
- package/dist/types/packages/desk/src/theme.d.ts +475 -0
- package/dist/types/packages/desk/src/tray/carry.d.ts +99 -0
- package/dist/types/packages/desk/src/tray/drawer.d.ts +113 -0
- package/dist/types/packages/desk/src/tray/flux.d.ts +46 -0
- package/dist/types/packages/desk/src/tray/index.d.ts +4 -0
- package/dist/types/packages/desk/src/tray/lattice.d.ts +71 -0
- package/dist/types/packages/desk/src/tray/layout.d.ts +7 -0
- package/dist/types/packages/desk/src/tray/look.d.ts +29 -0
- package/dist/types/packages/desk/src/tray/pass.d.ts +96 -0
- package/dist/types/packages/desk/src/tray/shaders.d.ts +10 -0
- package/dist/types/packages/desk/src/tray/specimens.d.ts +199 -0
- package/dist/types/packages/devtools/src/attach.d.ts +22 -8
- package/dist/types/packages/devtools/src/dock.d.ts +18 -14
- package/dist/types/packages/devtools/src/gpu-panel.d.ts +105 -0
- package/dist/types/packages/devtools/src/index.d.ts +5 -4
- package/dist/types/packages/dom/src/desk-host.d.ts +91 -0
- package/dist/types/packages/dom/src/host.d.ts +13 -14
- package/dist/types/packages/dom/src/index.d.ts +12 -10
- package/dist/types/packages/dom/src/loop.d.ts +18 -1
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +5 -29
- package/dist/types/packages/dom/src/widget-focus.d.ts +17 -13
- package/dist/types/packages/ice/src/desk-engine.d.ts +2 -0
- package/dist/types/packages/ice/src/desk-kit.d.ts +6 -0
- package/dist/types/packages/ice/src/desk-objects.d.ts +5 -0
- package/dist/types/packages/ice/src/desk.d.ts +5 -0
- package/dist/types/packages/kernel/src/atlas-pack.d.ts +157 -0
- package/dist/types/packages/kernel/src/bezier.d.ts +0 -15
- package/dist/types/packages/kernel/src/coords.d.ts +0 -39
- package/dist/types/packages/kernel/src/index.d.ts +2 -2
- package/dist/types/packages/kernel/src/nav-flight.d.ts +6 -0
- package/dist/types/packages/kernel/src/tray.d.ts +155 -0
- package/dist/types/packages/objects/src/board/board-pass.d.ts +180 -0
- package/dist/types/packages/objects/src/board/board-pen.d.ts +36 -0
- package/dist/types/packages/objects/src/board/board.d.ts +104 -0
- package/dist/types/packages/objects/src/board/data.d.ts +31 -0
- package/dist/types/packages/objects/src/board/history.d.ts +33 -0
- package/dist/types/packages/objects/src/board/kind.d.ts +208 -0
- package/dist/types/packages/objects/src/board/layout.d.ts +102 -0
- package/dist/types/packages/objects/src/board/object.d.ts +7 -0
- package/dist/types/packages/objects/src/board/pen.d.ts +71 -0
- package/dist/types/packages/objects/src/board/shaders.d.ts +36 -0
- package/dist/types/packages/objects/src/board/stroke.d.ts +99 -0
- package/dist/types/packages/objects/src/board/theme.d.ts +150 -0
- package/dist/types/packages/objects/src/calendar/data.d.ts +97 -0
- package/dist/types/packages/objects/src/calendar/events.d.ts +58 -0
- package/dist/types/packages/objects/src/calendar/hand.d.ts +53 -0
- package/dist/types/packages/objects/src/calendar/host/input.d.ts +67 -0
- package/dist/types/packages/objects/src/calendar/host/print.d.ts +9 -0
- package/dist/types/packages/objects/src/calendar/kind.d.ts +297 -0
- package/dist/types/packages/objects/src/calendar/law.d.ts +151 -0
- package/dist/types/packages/objects/src/calendar/layout.d.ts +47 -0
- package/dist/types/packages/objects/src/calendar/month.d.ts +75 -0
- package/dist/types/packages/objects/src/calendar/object.d.ts +45 -0
- package/dist/types/packages/objects/src/calendar/pad.d.ts +42 -0
- package/dist/types/packages/objects/src/calendar/pass.d.ts +229 -0
- package/dist/types/packages/objects/src/calendar/print.d.ts +87 -0
- package/dist/types/packages/objects/src/calendar/printing.d.ts +80 -0
- package/dist/types/packages/objects/src/calendar/roll.d.ts +45 -0
- package/dist/types/packages/objects/src/calendar/shaders.d.ts +12 -0
- package/dist/types/packages/objects/src/calendar/sheet.d.ts +43 -0
- package/dist/types/packages/objects/src/calendar/tiles.d.ts +67 -0
- package/dist/types/packages/objects/src/calendar/turn.d.ts +73 -0
- package/dist/types/packages/objects/src/calendar/writing.d.ts +42 -0
- package/dist/types/packages/objects/src/index.d.ts +22 -0
- package/dist/types/packages/objects/src/kinds.d.ts +24 -0
- package/dist/types/packages/objects/src/minimat/inside.d.ts +17 -0
- package/dist/types/packages/objects/src/minimat/kind.d.ts +51 -0
- package/dist/types/packages/objects/src/minimat/layout.d.ts +83 -0
- package/dist/types/packages/objects/src/minimat/minimat.d.ts +179 -0
- package/dist/types/packages/objects/src/minimat/object.d.ts +8 -0
- package/dist/types/packages/objects/src/minimat/pass.d.ts +61 -0
- package/dist/types/packages/objects/src/minimat/shaders.d.ts +10 -0
- package/dist/types/packages/objects/src/minimat/theme.d.ts +98 -0
- package/dist/types/packages/objects/src/notebook/ink.d.ts +70 -0
- package/dist/types/packages/objects/src/notebook/kind.d.ts +236 -0
- package/dist/types/packages/objects/src/notebook/law.d.ts +129 -0
- package/dist/types/packages/objects/src/notebook/layout.d.ts +46 -0
- package/dist/types/packages/objects/src/notebook/leaf.d.ts +44 -0
- package/dist/types/packages/objects/src/notebook/mesh.d.ts +20 -0
- package/dist/types/packages/objects/src/notebook/motion.d.ts +107 -0
- package/dist/types/packages/objects/src/notebook/object.d.ts +8 -0
- package/dist/types/packages/objects/src/notebook/pages.d.ts +56 -0
- package/dist/types/packages/objects/src/notebook/pass.d.ts +197 -0
- package/dist/types/packages/objects/src/notebook/pick.d.ts +30 -0
- package/dist/types/packages/objects/src/notebook/raster.d.ts +28 -0
- package/dist/types/packages/objects/src/notebook/shaders.d.ts +11 -0
- package/dist/types/packages/objects/src/notebook/shape.d.ts +155 -0
- package/dist/types/packages/objects/src/notebook/theme.d.ts +76 -0
- package/dist/types/packages/objects/src/palette.d.ts +577 -0
- package/dist/types/packages/objects/src/paper/host/body.d.ts +30 -0
- package/dist/types/packages/objects/src/paper/kind.d.ts +81 -0
- package/dist/types/packages/objects/src/paper/layout.d.ts +73 -0
- package/dist/types/packages/objects/src/paper/object.d.ts +31 -0
- package/dist/types/packages/objects/src/paper/pages.d.ts +43 -0
- package/dist/types/packages/objects/src/paper/paper-pass.d.ts +86 -0
- package/dist/types/packages/objects/src/paper/paper.d.ts +117 -0
- package/dist/types/packages/objects/src/paper/shaders.d.ts +18 -0
- package/dist/types/packages/objects/src/paper/theme.d.ts +58 -0
- package/dist/types/packages/objects/src/paper/typing.d.ts +51 -0
- package/dist/types/packages/objects/src/paper/writing.d.ts +161 -0
- package/dist/types/packages/objects/src/photo/carry.d.ts +33 -0
- package/dist/types/packages/objects/src/photo/kind.d.ts +136 -0
- package/dist/types/packages/objects/src/photo/layout.d.ts +39 -0
- package/dist/types/packages/objects/src/photo/object.d.ts +5 -0
- package/dist/types/packages/objects/src/photo/photo-pass.d.ts +104 -0
- package/dist/types/packages/objects/src/photo/photo.d.ts +262 -0
- package/dist/types/packages/objects/src/photo/pictures.d.ts +152 -0
- package/dist/types/packages/objects/src/photo/sample.d.ts +9 -0
- package/dist/types/packages/objects/src/photo/shaders.d.ts +18 -0
- package/dist/types/packages/objects/src/preset.d.ts +44 -0
- package/dist/types/packages/objects/src/shaders.d.ts +3 -0
- package/dist/types/packages/objects/src/shaders.gen.d.ts +21 -0
- package/dist/types/packages/react/src/canvas-hooks.d.ts +12 -0
- package/dist/types/packages/react/src/desk.d.ts +56 -0
- package/dist/types/packages/react/src/engine-context.d.ts +2 -2
- package/dist/types/packages/react/src/frame-preview-boundary.d.ts +19 -0
- package/dist/types/packages/react/src/hooks.d.ts +9 -14
- package/dist/types/packages/react/src/index.d.ts +16 -9
- package/dist/types/packages/react/src/keymap.d.ts +21 -2
- package/dist/types/packages/react/src/selection-menu.d.ts +171 -0
- package/dist/types/packages/react/src/tray-bar.d.ts +124 -0
- package/package.json +64 -31
- package/dist/chunk-5FQS7V2C.js.map +0 -1
- package/dist/chunk-JWRHAM7L.js +0 -1163
- package/dist/chunk-JWRHAM7L.js.map +0 -1
- package/dist/chunk-MFTMPJUF.js +0 -681
- package/dist/chunk-MFTMPJUF.js.map +0 -1
- package/dist/chunk-OR5FNVUE.js.map +0 -1
- package/dist/ground.js +0 -1213
- package/dist/ground.js.map +0 -1
- package/dist/r3f.js +0 -1644
- package/dist/r3f.js.map +0 -1
- package/dist/types/packages/core/src/input/measure-queue.d.ts +0 -30
- package/dist/types/packages/core/src/settings/ground-config.d.ts +0 -103
- package/dist/types/packages/core/src/systems/measure-ingest.d.ts +0 -26
- package/dist/types/packages/devtools/src/gl-panel.d.ts +0 -69
- package/dist/types/packages/dom/src/measure-adapter.d.ts +0 -31
- package/dist/types/packages/dom/src/measure-wiring.d.ts +0 -49
- package/dist/types/packages/dom/src/planes.d.ts +0 -33
- package/dist/types/packages/dom/src/reflectors/chrome.d.ts +0 -33
- package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +0 -59
- package/dist/types/packages/dom/src/reflectors/graybox.d.ts +0 -31
- package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +0 -38
- package/dist/types/packages/ground/src/index.d.ts +0 -69
- package/dist/types/packages/ground/src/layer.d.ts +0 -40
- package/dist/types/packages/ground/src/pass.d.ts +0 -41
- package/dist/types/packages/ground/src/passes/grid-classic.d.ts +0 -30
- package/dist/types/packages/ground/src/passes/grid-magnet.d.ts +0 -31
- package/dist/types/packages/ground/src/passes/grid.d.ts +0 -28
- package/dist/types/packages/ground/src/passes/guides-collect.d.ts +0 -14
- package/dist/types/packages/ground/src/passes/guides.d.ts +0 -3
- package/dist/types/packages/ground/src/passes/magnet-collect.d.ts +0 -63
- package/dist/types/packages/ground/src/passes/soup-collect.d.ts +0 -35
- package/dist/types/packages/ground/src/passes/soup-mesh.d.ts +0 -8
- package/dist/types/packages/ground/src/passes/wires-collect.d.ts +0 -35
- package/dist/types/packages/ground/src/passes/wires.d.ts +0 -10
- package/dist/types/packages/ground/src/poles.d.ts +0 -47
- package/dist/types/packages/ground/src/renderer.d.ts +0 -15
- package/dist/types/packages/ice/src/ground.d.ts +0 -1
- package/dist/types/packages/ice/src/r3f.d.ts +0 -1
- package/dist/types/packages/kernel/src/eviction.d.ts +0 -23
- package/dist/types/packages/kernel/src/zoom-bands.d.ts +0 -19
- package/dist/types/packages/r3f/src/bridge.d.ts +0 -129
- package/dist/types/packages/r3f/src/composite-material.d.ts +0 -26
- package/dist/types/packages/r3f/src/compositor-pass.d.ts +0 -105
- package/dist/types/packages/r3f/src/dev-write-trap.d.ts +0 -30
- package/dist/types/packages/r3f/src/gl-root.d.ts +0 -96
- package/dist/types/packages/r3f/src/gl-router.d.ts +0 -70
- package/dist/types/packages/r3f/src/index.d.ts +0 -17
- package/dist/types/packages/r3f/src/island-state.d.ts +0 -112
- package/dist/types/packages/r3f/src/island.d.ts +0 -13
- package/dist/types/packages/r3f/src/pool.d.ts +0 -56
- package/dist/types/packages/r3f/src/preview-capture.d.ts +0 -19
- package/dist/types/packages/r3f/src/resource-registry.d.ts +0 -30
- package/dist/types/packages/r3f/src/self-sustain.d.ts +0 -39
- package/dist/types/packages/r3f/src/use-island-frame.d.ts +0 -39
- package/dist/types/packages/react/src/infinite-canvas.d.ts +0 -140
- package/dist/types/packages/react/src/preview-snapshots.d.ts +0 -17
- package/dist/types/packages/react/src/widget-preview.d.ts +0 -14
- package/dist/types/packages/react/src/widget-root.d.ts +0 -33
|
@@ -0,0 +1,2429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bindLayout,
|
|
3
|
+
defineStruct
|
|
4
|
+
} from "./chunk-QXOCF56A.js";
|
|
5
|
+
import {
|
|
6
|
+
ChildOf,
|
|
7
|
+
HeldPress,
|
|
8
|
+
LocalPointer,
|
|
9
|
+
Pointer,
|
|
10
|
+
defineComponent,
|
|
11
|
+
definePrefab,
|
|
12
|
+
defineQuery,
|
|
13
|
+
field,
|
|
14
|
+
init
|
|
15
|
+
} from "./chunk-DYK6KNLG.js";
|
|
16
|
+
|
|
17
|
+
// ../desk/src/lattice/lod.ts
|
|
18
|
+
var BASE_SPACING = 20;
|
|
19
|
+
var LOD_TARGET = 20;
|
|
20
|
+
var ZOOM_MIN = 1e-8;
|
|
21
|
+
var ZOOM_MAX = 1e8;
|
|
22
|
+
var boxOf = (v) => v.box ?? { x: 0, y: 0, w: v.width, h: v.height };
|
|
23
|
+
var boxValues = (v) => {
|
|
24
|
+
const b = boxOf(v);
|
|
25
|
+
return [b.x, b.y, b.w, b.h];
|
|
26
|
+
};
|
|
27
|
+
var smoothstep = (a, b, x) => {
|
|
28
|
+
const t = Math.min(Math.max((x - a) / Math.max(b - a, 1e-3), 0), 1);
|
|
29
|
+
return t * t * (3 - 2 * t);
|
|
30
|
+
};
|
|
31
|
+
function lod(v) {
|
|
32
|
+
const screenCell = BASE_SPACING * v.zoom;
|
|
33
|
+
const logc = Math.log(Math.max(screenCell, 1e-20) / LOD_TARGET) / Math.LN10;
|
|
34
|
+
const floorLog = Math.floor(logc);
|
|
35
|
+
const k0 = Math.min(12, Math.max(-12, 0 - floorLog));
|
|
36
|
+
const fade = logc - floorLog;
|
|
37
|
+
const mid = BASE_SPACING * 10 ** k0;
|
|
38
|
+
return { k0, fade, fine: mid / 10, mid, coarse: mid * 10, wrapPeriod: mid * 10 };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ../desk/src/mat/night.ts
|
|
42
|
+
var DAY_LIGHT = { night: 0, lux: 0, exposure: 0, snow: 0, moon: [1, 1, 1], fill: 0, rod: [1, 1, 1], eigengrau: [0, 0, 0] };
|
|
43
|
+
var RGB2XYZ = [[0.4124564, 0.3575761, 0.1804375], [0.2126729, 0.7151522, 0.072175], [0.0193339, 0.119192, 0.9503041]];
|
|
44
|
+
var XYZ2RGB = [[3.2404542, -1.5371385, -0.4985314], [-0.969266, 1.8760108, 0.041556], [0.0556434, -0.2040259, 1.0572252]];
|
|
45
|
+
var mul = (M, v) => [M[0][0] * v[0] + M[0][1] * v[1] + M[0][2] * v[2], M[1][0] * v[0] + M[1][1] * v[1] + M[1][2] * v[2], M[2][0] * v[0] + M[2][1] * v[1] + M[2][2] * v[2]];
|
|
46
|
+
var tri = (f) => [f(0), f(1), f(2)];
|
|
47
|
+
var srgbToLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
48
|
+
var linear = (c) => [srgbToLinear(c[0]), srgbToLinear(c[1]), srgbToLinear(c[2])];
|
|
49
|
+
var luminance = (c) => RGB2XYZ[1][0] * c[0] + RGB2XYZ[1][1] * c[1] + RGB2XYZ[1][2] * c[2];
|
|
50
|
+
function scotopic(c) {
|
|
51
|
+
const [X, Y, Z] = mul(RGB2XYZ, c);
|
|
52
|
+
return Y * (1.33 * (1 + (Y + Z) / Math.max(X, 1e-6)) - 1.68);
|
|
53
|
+
}
|
|
54
|
+
var SCOTOPIC_WHITE = scotopic([1, 1, 1]);
|
|
55
|
+
var rodSignal = (c) => Math.max(scotopic(c), 0) / SCOTOPIC_WHITE;
|
|
56
|
+
var mesopicWeight = (L) => Math.min(Math.max(0.767 + 0.3334 * Math.log10(Math.max(L, 1e-9)), 0), 1);
|
|
57
|
+
var degreeOfAdaptation = (LA, F) => F * (1 - 1 / 3.6 * Math.exp((-LA - 42) / 92));
|
|
58
|
+
function planckianXY(T) {
|
|
59
|
+
const t = 1 / T;
|
|
60
|
+
const t2 = t * t;
|
|
61
|
+
const t3 = t2 * t;
|
|
62
|
+
const x = T <= 4e3 ? -266123900 * t3 - 234358.9 * t2 + 877.6956 * t + 0.17991 : -3025846900 * t3 + 21070379e-1 * t2 + 222.6347 * t + 0.24039;
|
|
63
|
+
const x2 = x * x;
|
|
64
|
+
const x3 = x2 * x;
|
|
65
|
+
const y = T <= 2222 ? -1.1063814 * x3 - 1.3481102 * x2 + 2.18555832 * x - 0.20219683 : T <= 4e3 ? -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867 : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;
|
|
66
|
+
return [x, y];
|
|
67
|
+
}
|
|
68
|
+
var xyToLinear = (x, y) => mul(XYZ2RGB, [x / y, 1, (1 - x - y) / y]);
|
|
69
|
+
var D65 = [0.3127, 0.329];
|
|
70
|
+
var LOCUS = [[470, 0.1241, 0.0578], [475, 0.1096, 0.0868], [480, 0.0913, 0.1327], [485, 0.0687, 0.2007], [490, 0.0454, 0.295], [495, 0.0235, 0.4127], [500, 82e-4, 0.5384]];
|
|
71
|
+
function spectralXY(nm) {
|
|
72
|
+
const w = Math.min(Math.max(nm, LOCUS[0][0]), LOCUS[6][0]);
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < LOCUS.length - 2 && LOCUS[i + 1][0] <= w) i++;
|
|
75
|
+
const [w0, x0, y0] = LOCUS[i];
|
|
76
|
+
const [w1, x1, y1] = LOCUS[i + 1];
|
|
77
|
+
const t = (w - w0) / (w1 - w0);
|
|
78
|
+
return [x0 + (x1 - x0) * t, y0 + (y1 - y0) * t];
|
|
79
|
+
}
|
|
80
|
+
function hueTint(nm, purity) {
|
|
81
|
+
const [sx, sy] = spectralXY(nm);
|
|
82
|
+
return xyToLinear(D65[0] + (sx - D65[0]) * purity, D65[1] + (sy - D65[1]) * purity);
|
|
83
|
+
}
|
|
84
|
+
var dayLuminance = (groundSrgb) => luminance(linear(linear(groundSrgb)));
|
|
85
|
+
function nightReport(light, albedoLinear, shade) {
|
|
86
|
+
const cone = [albedoLinear[0] * light.moon[0] * shade, albedoLinear[1] * light.moon[1] * shade, albedoLinear[2] * light.moon[2] * shade];
|
|
87
|
+
const rod = rodSignal(cone);
|
|
88
|
+
const m = mesopicWeight(light.lux * luminance(cone) / Math.PI);
|
|
89
|
+
return tri((i) => (1 - m) * rod * light.rod[i] + m * cone[i]);
|
|
90
|
+
}
|
|
91
|
+
function nightLight(spec, groundSrgb) {
|
|
92
|
+
const ground = linear(groundSrgb);
|
|
93
|
+
const [px, py] = planckianXY(spec.kelvin);
|
|
94
|
+
const moonRaw = xyToLinear(px, py);
|
|
95
|
+
const D = degreeOfAdaptation(spec.lux * luminance(ground) / Math.PI, spec.surround);
|
|
96
|
+
const moon = [1 + (1 - D) * (moonRaw[0] - 1), 1 + (1 - D) * (moonRaw[1] - 1), 1 + (1 - D) * (moonRaw[2] - 1)];
|
|
97
|
+
const eigengrau = linear(spec.eigengrau);
|
|
98
|
+
const light = { night: 1, lux: spec.lux, exposure: 1, snow: spec.snow, moon, fill: spec.fill, rod: hueTint(spec.rodHue.nm, spec.rodHue.purity), eigengrau };
|
|
99
|
+
const above = Math.max(spec.litLuminance - luminance(eigengrau), 0);
|
|
100
|
+
return { ...light, exposure: above / Math.max(luminance(nightReport(light, ground, 1)), 1e-9) };
|
|
101
|
+
}
|
|
102
|
+
function lightValues(l) {
|
|
103
|
+
return {
|
|
104
|
+
night: [l.night, l.lux, l.exposure, l.snow],
|
|
105
|
+
moon: [l.moon[0], l.moon[1], l.moon[2], l.fill],
|
|
106
|
+
rod: [l.rod[0], l.rod[1], l.rod[2], 0],
|
|
107
|
+
eigengrau: [l.eigengrau[0], l.eigengrau[1], l.eigengrau[2], 0]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function mixLight(a, b, t) {
|
|
111
|
+
if (t <= 0) return a;
|
|
112
|
+
if (t >= 1) return b;
|
|
113
|
+
const l = (x, y) => x + (y - x) * t;
|
|
114
|
+
const c = (x, y) => [l(x[0], y[0]), l(x[1], y[1]), l(x[2], y[2])];
|
|
115
|
+
return { night: l(a.night, b.night), lux: l(a.lux, b.lux), exposure: l(a.exposure, b.exposure), snow: l(a.snow, b.snow), moon: c(a.moon, b.moon), fill: l(a.fill, b.fill), rod: c(a.rod, b.rod), eigengrau: c(a.eigengrau, b.eigengrau) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ../desk/src/theme.ts
|
|
119
|
+
function cssColor(css) {
|
|
120
|
+
const s = css.trim().toLowerCase();
|
|
121
|
+
let m = /^#([0-9a-f]{6})$/.exec(s);
|
|
122
|
+
if (m) {
|
|
123
|
+
const v = Number.parseInt(m[1], 16);
|
|
124
|
+
return [(v >> 16 & 255) / 255, (v >> 8 & 255) / 255, (v & 255) / 255, 1];
|
|
125
|
+
}
|
|
126
|
+
m = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)\s*(?:[/,]\s*([\d.]+)(%?))?\s*\)$/.exec(s);
|
|
127
|
+
if (m) {
|
|
128
|
+
const a = m[4] === void 0 ? 1 : m[5] === "%" ? Number(m[4]) / 100 : Number(m[4]);
|
|
129
|
+
return [Number(m[1]) / 255, Number(m[2]) / 255, Number(m[3]) / 255, a];
|
|
130
|
+
}
|
|
131
|
+
throw new Error(`theme: unparsed colour "${css}"`);
|
|
132
|
+
}
|
|
133
|
+
var rgb = (css) => cssColor(css).slice(0, 3);
|
|
134
|
+
var GRID = { fadeIn: [10, 20], dressFloor: 0.5 };
|
|
135
|
+
var MAT = {
|
|
136
|
+
ground: { token: "hero cuttingMatAlbedo `sage`", css: "#86a078" },
|
|
137
|
+
// 134 160 120
|
|
138
|
+
line: { token: "hero cuttingMatAlbedo `cream`", css: "#e8dcbe" },
|
|
139
|
+
// 232 220 190
|
|
140
|
+
/** The colour a desk object's shadow casts ON the mat — the tree-shadow design system's `--cast-rgb` (mat.css: "the world's darkest value"). */
|
|
141
|
+
cast: { token: "tree-shadow design mat.css `--cast-rgb`", css: "#0e1a0a" }
|
|
142
|
+
// 14 26 10
|
|
143
|
+
};
|
|
144
|
+
var MAT_COLORS = { ground: rgb(MAT.ground.css), line: rgb(MAT.line.css), cast: rgb(MAT.cast.css) };
|
|
145
|
+
var MAT_GRID = {
|
|
146
|
+
line: { thin: 0.5, thick: 0.75, alphaThin: 0.14, alphaThick: 0.28 },
|
|
147
|
+
/** ±8/255 on the green channel (0.4× red, 0.35× blue): static hash + drifting value noise + blue noise. */
|
|
148
|
+
grain: 8 / 255,
|
|
149
|
+
gobo: {
|
|
150
|
+
opacity: 1,
|
|
151
|
+
/** `getGoboBlurRatio(z, params) * 2.0` — the blur radius in plate texels at full softness. */
|
|
152
|
+
blurTexels: 2,
|
|
153
|
+
/** heroFrag: `shade = mix(1, coaster·(0.5 + 0.5·gobo), 0.975)`; then HSB s += (1−shade)·0.1, v *= shade. */
|
|
154
|
+
shadeMix: 0.975,
|
|
155
|
+
darkFloor: 0.5,
|
|
156
|
+
saturate: 0.1,
|
|
157
|
+
/** scenes.js `hero.params` — the blur ramp's two linear depths (metres from the projector). */
|
|
158
|
+
sharp: 0.372991,
|
|
159
|
+
soft: 0.79059,
|
|
160
|
+
/** The plates: R silhouette, G wind amp 1, B phase, A amps 2+3; each with its wind strength. */
|
|
161
|
+
plates: { c: { strength: 0.38847005 }, b: { strength: 0.4630597 } },
|
|
162
|
+
plate: "c",
|
|
163
|
+
/** gobo.js: `time += dt·(speed + 1 + r)` at speed 4; the lab's 0 = frozen. */
|
|
164
|
+
wind: 5,
|
|
165
|
+
/** hero `mouseStrength` — radians of projector tilt per unit of smoothed pointer NDC. */
|
|
166
|
+
tilt: 3e-3
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* The RULERS printed along the mat's top and left edges (RULER.md — James's mockup of
|
|
170
|
+
* 2026-09-23, measured at its 1.6×: a 41 px band under a 41 px margin, 12 px numerals,
|
|
171
|
+
* ticks 9 · 15 · 24, 1–2 px lines): one MODULE for the unprinted margin and one for the
|
|
172
|
+
* band; 1 px lines; the text 10 px mono (DESIGN.md §3's caption size, the product's
|
|
173
|
+
* `--vf-font-mono`) 5 px in from the outer line and 3 px after its tick; the ink the
|
|
174
|
+
* mat's own cream at these presences. The fine rung's ticks arrive over a 6 → 9 px pitch
|
|
175
|
+
* (a ruler's mm under its cm; at 5 the comb read busy) and a level's labels over 50 → 70 px,
|
|
176
|
+
* so labels sit 50–250 px apart at every zoom (lattice/ruler.ts).
|
|
177
|
+
*/
|
|
178
|
+
ruler: {
|
|
179
|
+
band: 26,
|
|
180
|
+
margin: 26,
|
|
181
|
+
tick: [6, 9, 15],
|
|
182
|
+
line: 1,
|
|
183
|
+
alpha: { frame: 0.55, tick: 0.85, label: 0.85 },
|
|
184
|
+
ticksFrom: [6, 9],
|
|
185
|
+
labelsFrom: [50, 70],
|
|
186
|
+
text: { size: 10, top: 5, gap: 3 },
|
|
187
|
+
maxChars: 9
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* World → desk: the hero's view at its default height is 0.2227 m over the
|
|
191
|
+
* window's height (900 px in the reference shots), so one world unit (a CSS
|
|
192
|
+
* px at zoom 1) is 0.2227 / 900 m and the dapple spans the same screen at
|
|
193
|
+
* zoom 1. The mat's centre (the reference's coaster) is world (0, 0); the
|
|
194
|
+
* desk plane is `y = 0.7471` in the projector's frame.
|
|
195
|
+
*/
|
|
196
|
+
plane: { metresPerUnit: 0.2227 / 900, originX: -0.0105761, originZ: -0.0485146, deskY: 0.7471 }
|
|
197
|
+
};
|
|
198
|
+
var NIGHT = {
|
|
199
|
+
/** Full moonlight is REDDER than sunlight — ≈ 4100 K against the Sun's ≈ 5500 (Ciocca & Wang 2013); the blue of a moonlit scene is the eye's, not the light's. */
|
|
200
|
+
kelvin: 4100,
|
|
201
|
+
/** A full Moon high in a clear sky: 0.1–0.3 lux on the ground, a million times under the Sun's. Where on the mesopic ramp the eye sits. */
|
|
202
|
+
lux: 0.2,
|
|
203
|
+
/** The sky's share in the shadow — the same air scatters the same fraction of the Moon as of the Sun, ≈ 0.15–0.2 of the direct light at 45°. */
|
|
204
|
+
fill: 0.18,
|
|
205
|
+
/** The rods' signal drawn as a colour: white pulled 18 % toward 485 nm — the low-purity blue the night-rendering literature gives scotopic vision (Jensen et al. 2000; Khan & Pattanaik 2004). */
|
|
206
|
+
rodHue: { nm: 485, purity: 0.18 },
|
|
207
|
+
/** Eigengrau — the "intrinsic grey" the eye reports with no light at all (the rods' own dark noise); the value the term is given. */
|
|
208
|
+
eigengrau: { token: "Eigengrau (the dark's own grey)", css: "#16161d" },
|
|
209
|
+
/** The appearance's one scale: the moonlit sage is drawn at HALF the sunlit one's displayed luminance — the million can't be shown; the adapted eye's report is. */
|
|
210
|
+
litOverDay: 0.5,
|
|
211
|
+
/** The rods' own noise on the display: ±2/255, blue noise, achromatic. */
|
|
212
|
+
snow: 4 / 255,
|
|
213
|
+
/** CIECAM02's surround factor for the eye's adaptation to the Moon's colour: 0.8, a dark surround. */
|
|
214
|
+
surround: 0.8
|
|
215
|
+
};
|
|
216
|
+
var EIGENGRAU = rgb(NIGHT.eigengrau.css);
|
|
217
|
+
var MAT_LIGHT = {
|
|
218
|
+
light: DAY_LIGHT,
|
|
219
|
+
dark: nightLight({
|
|
220
|
+
kelvin: NIGHT.kelvin,
|
|
221
|
+
lux: NIGHT.lux,
|
|
222
|
+
fill: NIGHT.fill,
|
|
223
|
+
rodHue: NIGHT.rodHue,
|
|
224
|
+
eigengrau: EIGENGRAU,
|
|
225
|
+
litLuminance: NIGHT.litOverDay * dayLuminance(MAT_COLORS.ground),
|
|
226
|
+
snow: NIGHT.snow,
|
|
227
|
+
surround: NIGHT.surround
|
|
228
|
+
}, MAT_COLORS.ground)
|
|
229
|
+
};
|
|
230
|
+
var PORTAL = { gate: [140, 220] };
|
|
231
|
+
var MARKS = {
|
|
232
|
+
/** The inks, each with its token (the page's §14; desk.css; chrome.js `INK`). */
|
|
233
|
+
inks: {
|
|
234
|
+
pencil: { token: "--desk-pencil (Marks on the Mat \xA714, proposed)", css: "#79b5f8" },
|
|
235
|
+
/** The keyline under a pencil stroke: the mat's cast at 34 % (`--desk-keyline`). */
|
|
236
|
+
keyline: { token: "--desk-keyline \u2014 the tree-shadow design's `--cast-rgb`", css: "rgb(14 26 10 / .34)" },
|
|
237
|
+
/** A resize knob's face: chrome.js `INK.paper`. */
|
|
238
|
+
paper: { token: "chrome.js INK.paper", css: "#fffdf8" },
|
|
239
|
+
laser: { token: "--desk-laser (the bloom, added)", css: "#ff2e93" },
|
|
240
|
+
laserLine: { token: "--desk-laser-line", css: "#ff4fa3" },
|
|
241
|
+
laserCore: { token: "--desk-laser-core (5.94:1 on the lit mat)", css: "#ffd6ea" },
|
|
242
|
+
/** The pencil pill's numerals: the tray's ink (`--desk-ink`, solid); the laser pill's: white. */
|
|
243
|
+
tray: { token: "--desk-ink, solid (the tree-shadow design's `--ink`)", css: "#100904" },
|
|
244
|
+
white: { token: "chrome.js `pill` \u2014 the laser pill's numerals", css: "#ffffff" },
|
|
245
|
+
tape: { token: "--desk-tape", css: "rgb(238 228 204 / .88)" },
|
|
246
|
+
/** The vellum's veil: cream by day, a cool milk by night (chrome.js `drawMarquee`). */
|
|
247
|
+
vellumDay: { token: "chrome.js vellum, day", css: "rgb(255 250 238 / .11)" },
|
|
248
|
+
vellumNight: { token: "chrome.js vellum, night", css: "rgb(214 226 255 / .07)" }
|
|
249
|
+
},
|
|
250
|
+
/** The keyline is this much wider than the stroke it lies under. */
|
|
251
|
+
keyline: { grow: 2 },
|
|
252
|
+
/** One object selected: the frame 6 out, brackets reaching 16 (≤ 30 % of a side, never under r + 2), 1.5 thick, the hairline between them one DEVICE px at 42 %, the corner r + 6 ≤ 10; the lock-on arrives from 8 further out, its presence full by 1/2.2 of the way; knobs (radius 3.5, a cast ring 1 wider at 28 %) where the object resizes; under 24 px on screen, one ring. */
|
|
253
|
+
select: { gap: 6, reach: 16, share: 0.3, minOverR: 2, stroke: 1.5, hair: 0.42, radiusMax: 10, arrive: 8, rise: 2.2, knob: 3.5, knobShadow: 0.28, knobStroke: 1.5, collapse: 24 },
|
|
254
|
+
/** A member of several: quiet ticks — 4 out, reaching 8, 1.25 thick, at 62 %; the union carries the weight. */
|
|
255
|
+
member: { gap: 4, reach: 8, radiusMax: 8, stroke: 1.25, alpha: 0.62 },
|
|
256
|
+
/** Several: one union, 10 out, square to the mat, its corner 4. */
|
|
257
|
+
union: { gap: 10, radius: 4 },
|
|
258
|
+
/** The vellum: its pencil edge at 80 % (one device px), brackets reaching 12 at its corners once both sides pass 14, the count riding the cursor at +14, +18. */
|
|
259
|
+
marquee: { edge: 0.8, reach: 12, minSide: 14, count: [14, 18] },
|
|
260
|
+
/** A number the desk states: 10 px mono, 8 wider than its text, 15 tall, corner 3.5. */
|
|
261
|
+
pill: { size: 10, pad: 8, height: 15, radius: 3.5 },
|
|
262
|
+
/** The tray's name tags as LABEL TAPE (K5b, D-K5b.7 — marks/layout.ts `tagMarks`): the embosser's near-black tape and the pale plastic of its raised capitals. */
|
|
263
|
+
label: { tape: "#17181b", letters: "#ecebe4" },
|
|
264
|
+
/**
|
|
265
|
+
* The laser: a faint line wall to wall (3 px at 12 %, then 1 device px + 0.5 at 34 %, both added), and between the
|
|
266
|
+
* objects it aligns, 14 past them, the bloom (9 · 5 · 2.6 px at 7 · 16 · 32 %, added) under a 1.25 px line at 95 %
|
|
267
|
+
* with a 0.6 px core at 95 %; a centre is dotted (a dot every 4.51 px: the bloom 0.7 as wide, the line 2, the core 1);
|
|
268
|
+
* a new alignment strikes at 1.8×; a flare (radius 7) at every aligned corner; a gap, a 4 px bloom at 18 % under a
|
|
269
|
+
* 1.1 px line with 4 px ticks at its ends, its number in a laser pill on it (off it, under 26 px).
|
|
270
|
+
*/
|
|
271
|
+
laser: {
|
|
272
|
+
wall: [[3, 0.12], [0.5, 0.34]],
|
|
273
|
+
bloom: [[9, 0.07], [5, 0.16], [2.6, 0.32]],
|
|
274
|
+
line: 1.25,
|
|
275
|
+
core: 0.6,
|
|
276
|
+
alpha: 0.95,
|
|
277
|
+
dotted: { period: 4.51, bloom: 0.7, line: 2, core: 1 },
|
|
278
|
+
past: 14,
|
|
279
|
+
strike: 0.8,
|
|
280
|
+
flare: { radius: 7, stop: 0.25, core: 0.9, mid: 0.55 },
|
|
281
|
+
bar: { bloom: [4, 0.18], line: 1.1, tick: 4, pillFrom: 26, off: [16, 14] }
|
|
282
|
+
},
|
|
283
|
+
/** Your extent on the rulers (the mat's RULER band): a pencil wash at 16 %, its edges at 95 %, the edge coordinates 4 off them, 7 up from the band's inner line, over a 3 px cast halo at 55 %; a guide ticks the band in laser (1.5 px at 90 %, added). */
|
|
284
|
+
ruler: { wash: 0.16, edge: 0.95, label: { gap: 4, lift: 7, halo: 1.5, haloAlpha: 0.55 }, tick: { width: 1.5, alpha: 0.9, inset: 3 } },
|
|
285
|
+
/** Masking tape (desk.css `.obj-tape`): two strips 60 × 17 (object units) crossing the top corners, 5 over the top edge and 20 past each side, turned 37°; fibres 1 in 3 at 10 % white, a light from above (18 % white → 4 % black); moonlit by night. */
|
|
286
|
+
tape: { w: 60, h: 17, over: 5, past: 20, turn: 37, fibre: { every: 3, alpha: 0.1 }, sheen: [0.18, 0.04], night: { saturate: 0.3, brightness: 0.46 }, press: 1.22 },
|
|
287
|
+
/** The clocks, ms: the lock-on (on `--vf-ease-lift`) and the leave; several's union and its fade; the vellum's fold (`--vf-ease-island`); the strike; the tape pressed (the second strip 60 later) and lifted; the tape's GIVE — a drag that meets it shivers 2.2 px and settles. */
|
|
288
|
+
clocks: { lockOn: 180, leave: 120, union: 220, unionFade: 120, fold: 240, strike: 160, tapePress: 240, tapeStagger: 60, tapeLift: 160, give: 360 },
|
|
289
|
+
give: { px: 2.2, rate: 44 }
|
|
290
|
+
};
|
|
291
|
+
var TRAY = {
|
|
292
|
+
face: { token: "research/sdf-pegboard preview.jpg \u2014 the face, lit flat", css: "#cdb491" },
|
|
293
|
+
/** shader.js FACE_ALB · EDGE_ALB, linear. */
|
|
294
|
+
research: { face: [0.42, 0.26, 0.135], edge: [0.56, 0.41, 0.25] },
|
|
295
|
+
cavity: { edge: 0.9, heart: 1, width: 0.1 },
|
|
296
|
+
lamp: { azimuth: -36, elevation: 48 },
|
|
297
|
+
lampSize: 0.06,
|
|
298
|
+
/**
|
|
299
|
+
* K5a — the ACCESSORIES the specimens hang on (hooks, shelves, clips, rails): SKÅDIS's white powder coat warmed to sit on the
|
|
300
|
+
* hardboard; how far each stands off the board (pitches — its shadow's reach on the board under the lamp); the shadow's strength.
|
|
301
|
+
*/
|
|
302
|
+
accessory: { token: "SK\xC5DIS white, warmed", css: "#e8e3d9", height: { hook: 0.45, shelf: 0.6, clip: 0.3, rail: 0.4 }, shadow: 0.38 },
|
|
303
|
+
/** K5a — a specimen under the mouse lifts this share of the hold's lift (its own kind's `lift`): its shadow grows. */
|
|
304
|
+
hoverLift: 0.3
|
|
305
|
+
};
|
|
306
|
+
function themeFrom(name, p) {
|
|
307
|
+
return { name, canvasBg: rgb(p.canvasBg.css), select: rgb(p.select.css), matLight: MAT_LIGHT[name] };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ../desk/src/nav/portal.ts
|
|
311
|
+
var PORTAL_CHAIN = 6;
|
|
312
|
+
var PORTAL_CHAIN_TYPE = `array<vec4f, ${PORTAL_CHAIN}>`;
|
|
313
|
+
function chainOf(p) {
|
|
314
|
+
const out = [];
|
|
315
|
+
if (p?.portal) out.push(p.portal);
|
|
316
|
+
if (p?.within) out.push(...p.within);
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
function portalValues(p) {
|
|
320
|
+
const portals = new Array(4 * PORTAL_CHAIN).fill(0);
|
|
321
|
+
const clips = new Array(4 * PORTAL_CHAIN).fill(0);
|
|
322
|
+
const chain = chainOf(p);
|
|
323
|
+
if (chain.length > PORTAL_CHAIN) throw new Error(`nav/portal: a chain of ${chain.length} faces exceeds PORTAL_CHAIN ${PORTAL_CHAIN}`);
|
|
324
|
+
chain.forEach((c, i) => {
|
|
325
|
+
portals[4 * i] = c.cx;
|
|
326
|
+
portals[4 * i + 1] = c.cy;
|
|
327
|
+
portals[4 * i + 2] = c.hx;
|
|
328
|
+
portals[4 * i + 3] = c.hy;
|
|
329
|
+
clips[4 * i] = c.r;
|
|
330
|
+
clips[4 * i + 1] = 1;
|
|
331
|
+
clips[4 * i + 2] = c.feather ?? 0;
|
|
332
|
+
});
|
|
333
|
+
return { portals, clips };
|
|
334
|
+
}
|
|
335
|
+
function clipOf(K, radius, cam) {
|
|
336
|
+
const z = cam.zoom;
|
|
337
|
+
return { cx: (K.x + K.width / 2 - cam.x) * z, cy: (K.y + K.height / 2 - cam.y) * z, hx: K.width / 2 * z, hy: K.height / 2 * z, r: radius * z };
|
|
338
|
+
}
|
|
339
|
+
function boxOfPortal(c, attach) {
|
|
340
|
+
const x0 = Math.max(0, c.cx - c.hx - 1);
|
|
341
|
+
const y0 = Math.max(0, c.cy - c.hy - 1);
|
|
342
|
+
const x1 = Math.min(attach.width, c.cx + c.hx + 1);
|
|
343
|
+
const y1 = Math.min(attach.height, c.cy + c.hy + 1);
|
|
344
|
+
return { x: x0, y: y0, w: Math.max(0, x1 - x0), h: Math.max(0, y1 - y0) };
|
|
345
|
+
}
|
|
346
|
+
function intersectBox(a, b) {
|
|
347
|
+
const x0 = Math.max(a.x, b.x);
|
|
348
|
+
const y0 = Math.max(a.y, b.y);
|
|
349
|
+
const x1 = Math.min(a.x + a.w, b.x + b.w);
|
|
350
|
+
const y1 = Math.min(a.y + a.h, b.y + b.h);
|
|
351
|
+
return { x: x0, y: y0, w: Math.max(0, x1 - x0), h: Math.max(0, y1 - y0) };
|
|
352
|
+
}
|
|
353
|
+
var PORTAL_GATE = PORTAL.gate;
|
|
354
|
+
var PORTAL_CAP = 16;
|
|
355
|
+
var smoothstep2 = (a, b, x) => {
|
|
356
|
+
const t = Math.min(Math.max((x - a) / Math.max(b - a, 1e-3), 0), 1);
|
|
357
|
+
return t * t * (3 - 2 * t);
|
|
358
|
+
};
|
|
359
|
+
var portalPresence = (c, gate = PORTAL_GATE) => smoothstep2(gate[0], gate[1], 2 * Math.min(c.hx, c.hy));
|
|
360
|
+
function faceCovers(c, vp, margin) {
|
|
361
|
+
const rr = Math.min(c.r, c.hx, c.hy);
|
|
362
|
+
const sd = (x, y) => {
|
|
363
|
+
const qx = Math.abs(x - c.cx) - c.hx + rr;
|
|
364
|
+
const qy = Math.abs(y - c.cy) - c.hy + rr;
|
|
365
|
+
return Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - rr;
|
|
366
|
+
};
|
|
367
|
+
return sd(0, 0) <= -margin && sd(vp.width, 0) <= -margin && sd(0, vp.height) <= -margin && sd(vp.width, vp.height) <= -margin;
|
|
368
|
+
}
|
|
369
|
+
function scissorOf(p, dpr, size) {
|
|
370
|
+
let x0 = 0;
|
|
371
|
+
let y0 = 0;
|
|
372
|
+
let x1 = size.w;
|
|
373
|
+
let y1 = size.h;
|
|
374
|
+
for (const c of chainOf(p)) {
|
|
375
|
+
x0 = Math.max(x0, Math.floor((c.cx - c.hx) * dpr) - 1);
|
|
376
|
+
y0 = Math.max(y0, Math.floor((c.cy - c.hy) * dpr) - 1);
|
|
377
|
+
x1 = Math.min(x1, Math.ceil((c.cx + c.hx) * dpr) + 1);
|
|
378
|
+
y1 = Math.min(y1, Math.ceil((c.cy + c.hy) * dpr) + 1);
|
|
379
|
+
}
|
|
380
|
+
return [x0, y0, Math.max(0, x1 - x0), Math.max(0, y1 - y0)];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ../desk/src/lattice/ruler.ts
|
|
384
|
+
var PER_WRAP = [100, 20, 10, 2, 1];
|
|
385
|
+
var MULT = [2, 1, 2, 1, 2];
|
|
386
|
+
var EXP_OFFSET = [0, 1, 1, 2, 2];
|
|
387
|
+
var UNITS = [1, 5, 10, 50, 100];
|
|
388
|
+
var mix = (a, b, t) => a + (b - a) * t;
|
|
389
|
+
function rulerLevels(l, zoom, law, maxAbs = 0) {
|
|
390
|
+
const af = smoothstep(law.ticksFrom[0], law.ticksFrom[1], l.fine * zoom);
|
|
391
|
+
const [minor, medium, major] = law.tick;
|
|
392
|
+
const base = [minor, medium, mix(minor, major, af), mix(medium, major, af), major];
|
|
393
|
+
const tickAlpha = [af, af, 1, 1, 1];
|
|
394
|
+
const out = [];
|
|
395
|
+
for (let i = 0; i < 5; i++) {
|
|
396
|
+
const spacing = l.fine * UNITS[i];
|
|
397
|
+
const pitch = spacing * zoom;
|
|
398
|
+
const mult = MULT[i];
|
|
399
|
+
const exp = l.k0 + EXP_OFFSET[i];
|
|
400
|
+
let labelAlpha = smoothstep(law.labelsFrom[0], law.labelsFrom[1], pitch);
|
|
401
|
+
const chars = labelChars(maxAbs, mult, exp);
|
|
402
|
+
if (chars > law.maxChars || chars * law.text.size * ADVANCE_EM + law.text.gap > pitch) labelAlpha = 0;
|
|
403
|
+
out.push({ spacing, pitch, mult, exp, tickLen: mix(base[i], major, labelAlpha), tickAlpha: tickAlpha[i], labelAlpha, perWrap: PER_WRAP[i] });
|
|
404
|
+
}
|
|
405
|
+
return out;
|
|
406
|
+
}
|
|
407
|
+
var ADVANCE_EM = 0.6;
|
|
408
|
+
function finestLabelled(levels) {
|
|
409
|
+
for (let i = 0; i < 5; i++) if (levels[i].labelAlpha > 0) return i;
|
|
410
|
+
return -1;
|
|
411
|
+
}
|
|
412
|
+
var wrapsOf = (cam, period) => Math.floor(cam / period);
|
|
413
|
+
function formatLabel(index, mult, exp) {
|
|
414
|
+
const M = index * mult;
|
|
415
|
+
if (M === 0) return "0";
|
|
416
|
+
const neg = M < 0;
|
|
417
|
+
let digits = String(Math.abs(M));
|
|
418
|
+
let text;
|
|
419
|
+
if (exp >= 0) text = digits + "0".repeat(exp);
|
|
420
|
+
else {
|
|
421
|
+
const p = -exp;
|
|
422
|
+
digits = digits.padStart(p + 1, "0");
|
|
423
|
+
const int = digits.slice(0, digits.length - p);
|
|
424
|
+
const frac = digits.slice(digits.length - p).replace(/0+$/, "");
|
|
425
|
+
text = frac ? `${int}.${frac}` : int;
|
|
426
|
+
}
|
|
427
|
+
return neg ? `-${text}` : text;
|
|
428
|
+
}
|
|
429
|
+
function labelChars(maxAbs, mult, exp) {
|
|
430
|
+
const index = Math.ceil(maxAbs / (mult * 10 ** exp));
|
|
431
|
+
return Math.max(formatLabel(index, mult, exp).length, formatLabel(-index, mult, exp).length);
|
|
432
|
+
}
|
|
433
|
+
function labelReach(cam, zoom, span) {
|
|
434
|
+
return Math.max(Math.abs(cam), Math.abs(cam + span / zoom));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ../desk/src/mat/projector.ts
|
|
438
|
+
var cells = (m) => m;
|
|
439
|
+
function perspective(fovDeg, aspect, near, far) {
|
|
440
|
+
const top = near * Math.tan(fovDeg * Math.PI / 360);
|
|
441
|
+
const height = 2 * top;
|
|
442
|
+
const width = aspect * height;
|
|
443
|
+
const left = -0.5 * width;
|
|
444
|
+
const right = left + width;
|
|
445
|
+
const bottom = top - height;
|
|
446
|
+
const x = 2 * near / (right - left);
|
|
447
|
+
const y = 2 * near / (top - bottom);
|
|
448
|
+
const a = (right + left) / (right - left);
|
|
449
|
+
const b = (top + bottom) / (top - bottom);
|
|
450
|
+
const c = -(far + near) / (far - near);
|
|
451
|
+
const d = -2 * far * near / (far - near);
|
|
452
|
+
const m = new Float32Array(16);
|
|
453
|
+
m[0] = x;
|
|
454
|
+
m[5] = y;
|
|
455
|
+
m[8] = a;
|
|
456
|
+
m[9] = b;
|
|
457
|
+
m[10] = c;
|
|
458
|
+
m[11] = -1;
|
|
459
|
+
m[14] = d;
|
|
460
|
+
return m;
|
|
461
|
+
}
|
|
462
|
+
function fromQuat(q) {
|
|
463
|
+
const [x, y, z, w] = q;
|
|
464
|
+
const x2 = x + x;
|
|
465
|
+
const y2 = y + y;
|
|
466
|
+
const z2 = z + z;
|
|
467
|
+
const xx = x * x2;
|
|
468
|
+
const xy = x * y2;
|
|
469
|
+
const xz = x * z2;
|
|
470
|
+
const yy = y * y2;
|
|
471
|
+
const yz = y * z2;
|
|
472
|
+
const zz = z * z2;
|
|
473
|
+
const wx = w * x2;
|
|
474
|
+
const wy = w * y2;
|
|
475
|
+
const wz = w * z2;
|
|
476
|
+
const m = new Float32Array(16);
|
|
477
|
+
m[0] = 1 - (yy + zz);
|
|
478
|
+
m[4] = xy - wz;
|
|
479
|
+
m[8] = xz + wy;
|
|
480
|
+
m[1] = xy + wz;
|
|
481
|
+
m[5] = 1 - (xx + zz);
|
|
482
|
+
m[9] = yz - wx;
|
|
483
|
+
m[2] = xz - wy;
|
|
484
|
+
m[6] = yz + wx;
|
|
485
|
+
m[10] = 1 - (xx + yy);
|
|
486
|
+
m[15] = 1;
|
|
487
|
+
return m;
|
|
488
|
+
}
|
|
489
|
+
function mul4(a, b) {
|
|
490
|
+
const m = new Float32Array(16);
|
|
491
|
+
for (let col = 0; col < 4; col++) for (let row = 0; row < 4; row++) {
|
|
492
|
+
let s = 0;
|
|
493
|
+
for (let k = 0; k < 4; k++) s += a[k * 4 + row] * b[col * 4 + k];
|
|
494
|
+
m[col * 4 + row] = s;
|
|
495
|
+
}
|
|
496
|
+
return m;
|
|
497
|
+
}
|
|
498
|
+
function rigidInverse(position, q) {
|
|
499
|
+
const r = fromQuat(q);
|
|
500
|
+
const m = new Float32Array(16);
|
|
501
|
+
for (let col = 0; col < 3; col++) for (let row = 0; row < 3; row++) m[col * 4 + row] = r[row * 4 + col];
|
|
502
|
+
const [px, py, pz] = position;
|
|
503
|
+
const e = cells(m);
|
|
504
|
+
m[12] = -(e[0] * px + e[4] * py + e[8] * pz);
|
|
505
|
+
m[13] = -(e[1] * px + e[5] * py + e[9] * pz);
|
|
506
|
+
m[14] = -(e[2] * px + e[6] * py + e[10] * pz);
|
|
507
|
+
m[15] = 1;
|
|
508
|
+
return m;
|
|
509
|
+
}
|
|
510
|
+
function projectorMatrix(spec) {
|
|
511
|
+
return mul4(perspective(spec.fovDeg, spec.aspect, spec.near, spec.far), rigidInverse(spec.position, spec.quaternion));
|
|
512
|
+
}
|
|
513
|
+
function quatFromEuler(x, y, z) {
|
|
514
|
+
const c1 = Math.cos(x / 2);
|
|
515
|
+
const c2 = Math.cos(y / 2);
|
|
516
|
+
const c3 = Math.cos(z / 2);
|
|
517
|
+
const s1 = Math.sin(x / 2);
|
|
518
|
+
const s2 = Math.sin(y / 2);
|
|
519
|
+
const s3 = Math.sin(z / 2);
|
|
520
|
+
return [
|
|
521
|
+
s1 * c2 * c3 + c1 * s2 * s3,
|
|
522
|
+
c1 * s2 * c3 - s1 * c2 * s3,
|
|
523
|
+
c1 * c2 * s3 + s1 * s2 * c3,
|
|
524
|
+
c1 * c2 * c3 - s1 * s2 * s3
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
function tilted(base, ex, ey, ez) {
|
|
528
|
+
return mul4(fromQuat(quatFromEuler(ex, ey, ez)), base);
|
|
529
|
+
}
|
|
530
|
+
var HERO_PROJECTOR = {
|
|
531
|
+
position: [0.466854, 0.992917, -0.252766],
|
|
532
|
+
quaternion: [0.16182, -0.776664, -0.221667, -0.566984],
|
|
533
|
+
fovDeg: 60,
|
|
534
|
+
aspect: 1,
|
|
535
|
+
near: 0.01,
|
|
536
|
+
far: 20
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// ../desk/src/mat/layout.ts
|
|
540
|
+
var MatUniforms = defineStruct("MatUniforms", [
|
|
541
|
+
["cam", "vec4f"],
|
|
542
|
+
// camX, camY (world, UNwrapped — the projector is fixed in world), zoom, dpr
|
|
543
|
+
["view", "vec4f"],
|
|
544
|
+
// cssW, cssH, grain time (s), the OBJECTS' presentation opacity (the slot's × their own presence — what every kind multiplies by)
|
|
545
|
+
["phase", "vec4f"],
|
|
546
|
+
// wrapped camX, camY (the lattice's phase), unused ×2
|
|
547
|
+
["rungs", "vec4f"],
|
|
548
|
+
// fine, mid, coarse spacing (world), unused
|
|
549
|
+
["lod", "vec4f"],
|
|
550
|
+
// fadeIn lo, hi (CSS px), unused ×2
|
|
551
|
+
["line", "vec4f"],
|
|
552
|
+
// half-width thin, thick (DEVICE px), alpha thin, thick
|
|
553
|
+
["grain", "vec4f"],
|
|
554
|
+
// amplitude, unused ×3
|
|
555
|
+
["ground", "vec4f"],
|
|
556
|
+
// the mat's green (sRGB), unused
|
|
557
|
+
["ink", "vec4f"],
|
|
558
|
+
// the line cream (sRGB), unused
|
|
559
|
+
["plane", "vec4f"],
|
|
560
|
+
// desk origin x, z (m), metres per world unit, desk y (m)
|
|
561
|
+
["goboMatrix", "mat4x4f"],
|
|
562
|
+
["goboParams", "vec4f"],
|
|
563
|
+
// sharp, soft, near, far — the blur ramp's depths and the projector's clip range
|
|
564
|
+
["gobo", "vec4f"],
|
|
565
|
+
// opacity, blur radius (plate texels), shade mix, dark floor
|
|
566
|
+
["grade", "vec4f"],
|
|
567
|
+
// saturation bump, unused ×3
|
|
568
|
+
["noise", "vec4f"],
|
|
569
|
+
// blue-noise offset x, y, texel size, unused
|
|
570
|
+
["wind", "vec4f"],
|
|
571
|
+
// gobo time (s), plate strength, unused ×2
|
|
572
|
+
["night", "vec4f"],
|
|
573
|
+
// the NIGHT (mat/night.ts): amount (0 = the day's chain, exactly), the Moon's lux, exposure, snow
|
|
574
|
+
["moon", "vec4f"],
|
|
575
|
+
// the Moon's colour as the adapted eye takes it (linear sRGB, Y = 1), the sky's share in the shadow
|
|
576
|
+
["rod", "vec4f"],
|
|
577
|
+
// the rods' signal drawn as a colour (linear sRGB, Y = 1), unused
|
|
578
|
+
["eigengrau", "vec4f"],
|
|
579
|
+
// the dark the eye adds (linear sRGB), unused
|
|
580
|
+
["portals", PORTAL_CHAIN_TYPE],
|
|
581
|
+
// the portal CHAIN (nav/portal.ts): each face's centre xy, half extents xy (CSS px)
|
|
582
|
+
["clips", PORTAL_CHAIN_TYPE],
|
|
583
|
+
// each face's corner radius, on (0/1 — a 0 ends the chain), unused ×2
|
|
584
|
+
["ruler", "vec4f"],
|
|
585
|
+
// the RULERS (RULER.md; lattice/ruler.ts): on (0/1), margin, band, line width (CSS px)
|
|
586
|
+
["rulerText", "vec4f"],
|
|
587
|
+
// the text's inset from the outer line, its gap after the tick, the major tick's length (CSS px), the atlas's texels per CSS px
|
|
588
|
+
["rulerAlpha", "vec4f"],
|
|
589
|
+
// ink presence: the frame's lines, the ticks, the labels, unused
|
|
590
|
+
["rulerOrigin", "vec4f"],
|
|
591
|
+
// the lattice's wrap count of camX, camY (exact integers), the finest labelled level (−1 = none), unused
|
|
592
|
+
["rulerGlyph", "vec4f"],
|
|
593
|
+
// the glyph atlas: cell width, cell height, advance, baseline (texels)
|
|
594
|
+
["rulerAtlas", "vec4f"],
|
|
595
|
+
// the glyph atlas: width, height, cap height (texels), glyph count
|
|
596
|
+
["rulerSites", "array<vec4f, 5>"],
|
|
597
|
+
// per level: spacing (world), tick length (CSS px), tick presence, label presence
|
|
598
|
+
["rulerFormat", "array<vec4f, 5>"],
|
|
599
|
+
// per level: mult, exp (a site's value = index · mult · 10^exp), wrap period over spacing, unused
|
|
600
|
+
["box", "vec4f"],
|
|
601
|
+
// the slot's box on the attachment: x, y, w, h (CSS px) — stats only; the mat is fullscreen under the scissor
|
|
602
|
+
["light", "vec4f"],
|
|
603
|
+
// the LIGHT (MINIMAT.md §4): the camera the lamp's gobo is sampled under — x, y, zoom — and the cross-fade toward `light2`
|
|
604
|
+
["light2", "vec4f"],
|
|
605
|
+
// the second light a flight hands the slot to: x, y, zoom, unused
|
|
606
|
+
["presence", "vec4f"]
|
|
607
|
+
// the SLOT's presentation opacity — what the mat itself is drawn at (K4a, K-L3: the one view block carries both), unused ×3
|
|
608
|
+
]);
|
|
609
|
+
var GLYPH_PAD = 2;
|
|
610
|
+
var GLYPHS = "0123456789-.ABCDEFGHIJKLMNOPQRSTUVWXYZ &'/\xB7#()+:";
|
|
611
|
+
var RULER_GLYPHS = 12;
|
|
612
|
+
var NO_GLYPHS = { scale: 1, cellW: 1, cellH: 1, advance: 1, baseline: 1, cap: 1, width: 1, height: 1, count: 0 };
|
|
613
|
+
var DEFAULT_MAT_CONFIG = {
|
|
614
|
+
line: MAT_GRID.line,
|
|
615
|
+
grain: MAT_GRID.grain,
|
|
616
|
+
ground: MAT_COLORS.ground,
|
|
617
|
+
ink: MAT_COLORS.line,
|
|
618
|
+
gobo: {
|
|
619
|
+
opacity: MAT_GRID.gobo.opacity,
|
|
620
|
+
blurTexels: MAT_GRID.gobo.blurTexels,
|
|
621
|
+
shadeMix: MAT_GRID.gobo.shadeMix,
|
|
622
|
+
darkFloor: MAT_GRID.gobo.darkFloor,
|
|
623
|
+
saturate: MAT_GRID.gobo.saturate,
|
|
624
|
+
sharp: MAT_GRID.gobo.sharp,
|
|
625
|
+
soft: MAT_GRID.gobo.soft,
|
|
626
|
+
plate: MAT_GRID.gobo.plate
|
|
627
|
+
},
|
|
628
|
+
plane: MAT_GRID.plane,
|
|
629
|
+
ruler: { on: false, ...MAT_GRID.ruler }
|
|
630
|
+
};
|
|
631
|
+
var HERO_MATRIX = projectorMatrix(HERO_PROJECTOR);
|
|
632
|
+
var STILL_MAT_FRAME = { time: 0, goboTime: 0, goboMatrix: HERO_MATRIX, noise: [0, 0] };
|
|
633
|
+
var NOISE_SIZE = 128;
|
|
634
|
+
var PLATE_SIZE = 512;
|
|
635
|
+
function matUniformValues(view, fadeIn, cfg, f, plateStrength, present, light = DAY_LIGHT, glyphs = NO_GLYPHS, lit) {
|
|
636
|
+
const l = lod(view);
|
|
637
|
+
const r = cfg.ruler;
|
|
638
|
+
const levels = rulerLevels(l, view.zoom, r, Math.max(labelReach(view.camX, view.zoom, view.width), labelReach(view.camY, view.zoom, view.height)));
|
|
639
|
+
return {
|
|
640
|
+
cam: [view.camX, view.camY, view.zoom, view.dpr],
|
|
641
|
+
view: [view.width, view.height, f.time, present === void 0 ? 1 : present.objects === void 0 ? present.opacity : present.opacity * present.objects],
|
|
642
|
+
phase: [(view.camX % l.wrapPeriod + l.wrapPeriod) % l.wrapPeriod, (view.camY % l.wrapPeriod + l.wrapPeriod) % l.wrapPeriod, 0, 0],
|
|
643
|
+
rungs: [l.fine, l.mid, l.coarse, 0],
|
|
644
|
+
lod: [fadeIn[0], Math.max(fadeIn[1], fadeIn[0] + 1e-3), 0, 0],
|
|
645
|
+
line: [cfg.line.thin, cfg.line.thick, cfg.line.alphaThin, cfg.line.alphaThick],
|
|
646
|
+
grain: [cfg.grain, 0, 0, 0],
|
|
647
|
+
ground: [cfg.ground[0], cfg.ground[1], cfg.ground[2], 0],
|
|
648
|
+
ink: [cfg.ink[0], cfg.ink[1], cfg.ink[2], 0],
|
|
649
|
+
plane: [cfg.plane.originX, cfg.plane.originZ, cfg.plane.metresPerUnit, cfg.plane.deskY],
|
|
650
|
+
goboMatrix: f.goboMatrix,
|
|
651
|
+
goboParams: [cfg.gobo.sharp, cfg.gobo.soft, HERO_PROJECTOR.near, HERO_PROJECTOR.far],
|
|
652
|
+
gobo: [cfg.gobo.opacity, cfg.gobo.blurTexels, cfg.gobo.shadeMix, cfg.gobo.darkFloor],
|
|
653
|
+
grade: [cfg.gobo.saturate, 0, 0, 0],
|
|
654
|
+
noise: [f.noise[0], f.noise[1], 1 / NOISE_SIZE, 0],
|
|
655
|
+
wind: [f.goboTime, plateStrength, 0, 0],
|
|
656
|
+
...lightValues(light),
|
|
657
|
+
...portalValues(present),
|
|
658
|
+
ruler: [r.on ? 1 : 0, r.margin, r.band, r.line],
|
|
659
|
+
rulerText: [r.text.top, r.text.gap, r.tick[2], glyphs.scale],
|
|
660
|
+
rulerAlpha: [r.alpha.frame, r.alpha.tick, r.alpha.label, 0],
|
|
661
|
+
rulerOrigin: [wrapsOf(view.camX, l.wrapPeriod), wrapsOf(view.camY, l.wrapPeriod), finestLabelled(levels), 0],
|
|
662
|
+
rulerGlyph: [glyphs.cellW, glyphs.cellH, glyphs.advance, glyphs.baseline],
|
|
663
|
+
rulerAtlas: [glyphs.width, glyphs.height, glyphs.cap, glyphs.count],
|
|
664
|
+
rulerSites: levels.flatMap((L) => [L.spacing, L.tickLen, L.tickAlpha, L.labelAlpha]),
|
|
665
|
+
rulerFormat: levels.flatMap((L) => [L.mult, L.exp, L.perWrap, 0]),
|
|
666
|
+
box: boxValues(view),
|
|
667
|
+
...slotLightValues(view, lit),
|
|
668
|
+
// the slot's own opacity, which the mat is drawn at; `view.w` above is the objects' (what the ground's `objectsOf` hands the kinds'
|
|
669
|
+
// `SlotContext.present`). One view block per slot carries both (K4a, design-016 K-L3), so every kind reads it as it always read its copy
|
|
670
|
+
presence: [present?.opacity ?? 1, 0, 0, 0]
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function settleLight(lit) {
|
|
674
|
+
if (!lit?.b) return lit;
|
|
675
|
+
const t = lit.t ?? 0;
|
|
676
|
+
return t <= 0 ? { a: lit.a } : t >= 1 ? { a: lit.b } : lit;
|
|
677
|
+
}
|
|
678
|
+
function litByOwn(view, lit) {
|
|
679
|
+
const s = settleLight(lit);
|
|
680
|
+
return !s || !s.b && s.a.x === view.camX && s.a.y === view.camY && s.a.zoom === view.zoom;
|
|
681
|
+
}
|
|
682
|
+
function slotLightValues(view, lit) {
|
|
683
|
+
const own = { x: view.camX, y: view.camY, zoom: view.zoom };
|
|
684
|
+
const s = settleLight(lit);
|
|
685
|
+
const a = s?.a ?? own;
|
|
686
|
+
const b = s?.b ?? a;
|
|
687
|
+
const t = s?.b ? s.t ?? 0 : 0;
|
|
688
|
+
return { light: [a.x, a.y, a.zoom, t], light2: [b.x, b.y, b.zoom, 0] };
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ../desk/src/mat/grid.ts
|
|
692
|
+
var DEFAULT_GRID = { fadeIn: GRID.fadeIn, mat: DEFAULT_MAT_CONFIG };
|
|
693
|
+
function dressScale(zoom, lodZoom) {
|
|
694
|
+
if (lodZoom === void 0 || !(lodZoom > 0) || lodZoom === zoom) return 1;
|
|
695
|
+
return Math.max(zoom / lodZoom, GRID.dressFloor);
|
|
696
|
+
}
|
|
697
|
+
function dressGrid(cfg, zoom, lodZoom) {
|
|
698
|
+
const s = dressScale(zoom, lodZoom);
|
|
699
|
+
if (s === 1) return cfg;
|
|
700
|
+
return { ...cfg, fadeIn: [cfg.fadeIn[0] * s, cfg.fadeIn[1] * s] };
|
|
701
|
+
}
|
|
702
|
+
var gridStats = (view, wind) => {
|
|
703
|
+
const l = lod(view);
|
|
704
|
+
return { k0: l.k0, fade: l.fade, wind };
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
// ../desk/src/kit/book.ts
|
|
708
|
+
var NbUniforms = defineStruct("NbUniforms", [
|
|
709
|
+
["eye", "vec4f"],
|
|
710
|
+
// the desk eye: foot x, y (world), height (world), zoom
|
|
711
|
+
["view", "vec4f"],
|
|
712
|
+
// viewport w, h (CSS px), the depth range's near and far (distance from the eye)
|
|
713
|
+
["light", "vec4f"],
|
|
714
|
+
// the sky's share, the lambert's cap, the dapple's share on paper, on cloth
|
|
715
|
+
["shadow", "vec4f"],
|
|
716
|
+
// σ at contact, σ per unit of height, the shadow's alpha on the mat, the contact's alpha
|
|
717
|
+
["shadow2", "vec4f"],
|
|
718
|
+
// the contact's σ, the height it lets go by, the shadow map's size (texels), dpr
|
|
719
|
+
["paper", "vec4f"],
|
|
720
|
+
// fibre, mottle, tooth, cockle
|
|
721
|
+
["rule", "vec4f"],
|
|
722
|
+
// pitch, dot radius, margin, unused
|
|
723
|
+
["cloth", "vec4f"],
|
|
724
|
+
// the weave's pitch, its relief, the sheen's exponent and strength
|
|
725
|
+
["castCol", "vec4f"],
|
|
726
|
+
// what a shadow is made of on this ground
|
|
727
|
+
["select", "vec4f"],
|
|
728
|
+
// the ring's colour, its width (CSS px)
|
|
729
|
+
["ruleInk", "vec4f"],
|
|
730
|
+
// the ruling's ink, alpha
|
|
731
|
+
["ring", "vec4f"]
|
|
732
|
+
// the ring's offset from the footprint (CSS px), unused ×3
|
|
733
|
+
]);
|
|
734
|
+
var NbBook = defineStruct("NbBook", [
|
|
735
|
+
["model", "mat4x4f"],
|
|
736
|
+
// book → world
|
|
737
|
+
["inv", "mat4x4f"],
|
|
738
|
+
// world → book
|
|
739
|
+
["light", "mat4x4f"],
|
|
740
|
+
// world → the shadow map's clip space
|
|
741
|
+
["lamp", "vec4f"],
|
|
742
|
+
// the unit direction to the lamp (world), the shadow map's layer
|
|
743
|
+
["sh", "vec4f"],
|
|
744
|
+
// a shadow texel (world), the map's depth range (world), mapped (0/1), the book's tallest point
|
|
745
|
+
["size", "vec4f"],
|
|
746
|
+
// cover width, height, board, fore-edge radius
|
|
747
|
+
["page", "vec4f"],
|
|
748
|
+
// unused, page height, page corner radius, the spine band's width
|
|
749
|
+
["open", "vec4f"],
|
|
750
|
+
// the swing θ, the gutter's relax, the spine's width, the squares
|
|
751
|
+
["look", "vec4f"],
|
|
752
|
+
// design, ruling, the ring's presence, a seed
|
|
753
|
+
["foot", "vec4f"],
|
|
754
|
+
// the front board lies on the desk (0/1), the cover is paper (0/1), a sheet's thickness, the book shades itself (0/1)
|
|
755
|
+
["recv", "vec4f"],
|
|
756
|
+
// the mat's rect this book darkens: x0, y0, x1, y1 (world)
|
|
757
|
+
["inkPage", "array<vec4f, 2>"],
|
|
758
|
+
// the pages with ink on the device (−1 = none), eight
|
|
759
|
+
["inkLayer", "array<vec4f, 2>"],
|
|
760
|
+
// … and the raster layer each is in
|
|
761
|
+
["col0", "vec4f"],
|
|
762
|
+
["col1", "vec4f"],
|
|
763
|
+
["col2", "vec4f"],
|
|
764
|
+
["col3", "vec4f"],
|
|
765
|
+
["col4", "vec4f"],
|
|
766
|
+
["col5", "vec4f"],
|
|
767
|
+
["col6", "vec4f"],
|
|
768
|
+
["col7", "vec4f"]
|
|
769
|
+
]);
|
|
770
|
+
|
|
771
|
+
// ../desk/src/shaders.gen.ts
|
|
772
|
+
var WGSL = {
|
|
773
|
+
"hold/hold.wgsl": "// THE HAND'S FOCUS (design-015 \xA78; D4b) \u2014 the desk behind the object in hand goes out of focus, and the\n// object is laid over it in its own light. Four fullscreen fragments over one triangle, one binding set:\n// - `fs_down` / `fs_up`: the dual-Kawase blur (Bj\xF8rge, ARM 2015) \u2014 a down pass takes the centre \xD74 and the\n// four diagonals at \xB1offset half-texels; an up pass eight taps at \xB1offset (the axes once, the diagonals\n// twice) \u2014 the desk's HALF-SIZE copy blurred through two levels and back, once per settled desk.\n// - `fs_desk`: the frame's background \u2014 the sharp half-size copy mixed with the blurred one by the carry\n// amount (half-way up, half the blur), dimmed by `dim` (8 % at the top).\n// - `fs_hand`: the object in hand, drawn into a transparent target with the kinds' own straight-alpha\n// \"source over\" \u2014 which, from a cleared target, accumulates PREMULTIPLIED colour \u2014 laid over the frame\n// (one, one \u2212 src alpha), through the READING LIGHT (Q-n): saturate then brighten, both linear in the\n// premultiplied colour, so the at-rest passes' bytes never move.\n// Pure of everything but its bindings (engine/shader.ts's rule): the CPU decides every number.\n\n@group(0) @binding(0) var<uniform> u: HoldUniforms;\n@group(0) @binding(1) var tex_a: texture_2d<f32>;\n@group(0) @binding(2) var tex_b: texture_2d<f32>;\n@group(0) @binding(3) var samp: sampler;\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) uv: vec2f,\n}\n\n/** One triangle over the whole attachment; uv (0, 0) at the top-left, as the texture's rows run. */\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> VSOut {\n var out: VSOut;\n let x = f32((vid << 1u) & 2u);\n let y = f32(vid & 2u);\n out.clip = vec4f(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);\n out.uv = vec2f(x, y);\n return out;\n}\n\n/** Dual Kawase, down: `u.texel.xy` = one texel of the SOURCE, `u.texel.z` = the offset in half-texels. */\n@fragment\nfn fs_down(in: VSOut) -> @location(0) vec4f {\n let o = u.texel.xy * 0.5 * u.texel.z;\n var c = textureSample(tex_a, samp, in.uv) * 4.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, -o.y));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, -o.y));\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, o.y));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, o.y));\n return c / 8.0;\n}\n\n/** Dual Kawase, up: the same offset, eight taps. */\n@fragment\nfn fs_up(in: VSOut) -> @location(0) vec4f {\n let o = u.texel.xy * 0.5 * u.texel.z;\n var c = textureSample(tex_a, samp, in.uv + vec2f(-o.x * 2.0, 0.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, o.y)) * 2.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(0.0, o.y * 2.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, o.y)) * 2.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x * 2.0, 0.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, -o.y)) * 2.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(0.0, -o.y * 2.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, -o.y)) * 2.0;\n return c / 12.0;\n}\n\n/** The desk out of focus: the sharp copy (tex_a) mixed with the blurred one (tex_b) by the carry `u.mix.x`, dimmed by `u.mix.y`. Opaque. */\n@fragment\nfn fs_desk(in: VSOut) -> @location(0) vec4f {\n let sharp = textureSample(tex_a, samp, in.uv).rgb;\n let soft = textureSample(tex_b, samp, in.uv).rgb;\n return vec4f(mix(sharp, soft, u.mix.x) * (1.0 - u.mix.y), 1.0);\n}\n\n/** The hand over the frame: premultiplied (tex_a), through the reading light \u2014 saturate `u.mix.z`, brightness `u.mix.w`. Blend (one, one \u2212 src alpha). */\n@fragment\nfn fs_hand(in: VSOut) -> @location(0) vec4f {\n let h = textureSample(tex_a, samp, in.uv);\n let lum = dot(h.rgb, vec3f(0.2126, 0.7152, 0.0722));\n let rgb = mix(vec3f(lum), h.rgb, u.mix.z) * u.mix.w;\n return vec4f(rgb, h.a);\n}\n",
|
|
774
|
+
"kit/book.wgsl": "// (The kit's since K4a, design-016 \xA75 \u2014 moved from notebook/notebook.wgsl: the desk calendar composes it too, `kitWgsl([..., \"book\"])`.)\n//\n// The NOTEBOOK \u2014 a real 3D book under the desk's one lamp (NOTEBOOK.md). A PURE module: the\n// uniform blocks, the book records and the textures arrive as parameters, and it names no\n// colour (a book's palette is its record's). mat.wgsl comes first: its noise, its colour chain\n// (`shade_mat` by day, `night_mat` by night) and its gobo (`sample_gobo`) are the desk's, so a\n// page under the palm is dappled and graded exactly as the mat beside it.\n//\n// The pieces: the desk eye's projection (`nb_clip`, eye.ts is the same arithmetic); the\n// shadow \u2014 PCSS on the book's own shadow map, whose penumbra grows with the gap between caster\n// and receiver by the desk's law (\u03C3 = \u03C30 + k\xB7height, the note's and the photo's); the\n// materials \u2014 bookcloth with a weave and a sheen, the cover's printed design, the ivory page\n// with its fibre, tooth, cockle and dots, the stacked edges of the sheets, the endpaper framed\n// by the cloth's turn-in; and the light \u2014 a lambert normalised so a flat face reads its own\n// albedo, the sky's share where the lamp is blocked, the leaves' dapple.\n\nconst NB_PI = 3.14159265;\n\nfn nb_sat(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }\n\n// ---- the desk eye: a world point \u2192 clip space (x, y 2\xB7zoom\xB7(p \u2212 E)/viewport; w = (H \u2212 z)/H; a perspective depth)\nfn nb_clip(k: NbUniforms, P: vec3f) -> vec4f {\n let H = k.eye.z;\n let d = H - P.z;\n let x = 2.0 * k.eye.w * (P.x - k.eye.x) / k.view.x;\n let y = -2.0 * k.eye.w * (P.y - k.eye.y) / k.view.y;\n let n = k.view.z;\n let f = k.view.w;\n let z = (d - n) * f / ((f - n) * H);\n return vec4f(x, y, z, d / H);\n}\n\n// A world point raised off the desk, in the projector's frame (mat.wgsl `desk_of` with a height).\nfn nb_desk_at(u: MatUniforms, P: vec3f) -> vec3f {\n return vec3f(u.plane.x + P.x * u.plane.z, u.plane.w + P.z * u.plane.z, u.plane.y + P.y * u.plane.z);\n}\n\n// ---- noise in a face's own world units: an integer hash (no sin \u2014 a page fragment reads a dozen of these),\n// value noise with its analytic gradient (so a bump costs one evaluation, not three)\nfn nb_hash(p: vec2i) -> f32 {\n var h = (u32(p.x) * 668265261u) ^ (u32(p.y) * 374761393u);\n h = (h ^ (h >> 15u)) * 2246822519u;\n h = (h ^ (h >> 13u)) * 3266489917u;\n h = h ^ (h >> 16u);\n return f32(h) * 2.3283064e-10;\n}\n\n// value (centred on 0), d/dx, d/dy\nfn nb_vn(p: vec2f) -> vec3f {\n let fl = floor(p);\n let i = vec2i(fl);\n let f = p - fl;\n let w = f * f * (3.0 - 2.0 * f);\n let dw = 6.0 * f * (1.0 - f);\n let a = nb_hash(i);\n let b = nb_hash(i + vec2i(1, 0));\n let c = nb_hash(i + vec2i(0, 1));\n let d = nb_hash(i + vec2i(1, 1));\n let k = a - b - c + d;\n return vec3f(a + (b - a) * w.x + (c - a) * w.y + k * w.x * w.y - 0.5, dw.x * (b - a + k * w.y), dw.y * (c - a + k * w.x));\n}\n\n// three octaves, the gradient carried through\nfn nb_fbm(p: vec2f) -> vec3f {\n let a = nb_vn(p);\n let b = nb_vn(p * 2.03 + vec2f(17.1, 3.7));\n let c = nb_vn(p * 4.11 + vec2f(-5.3, 11.9));\n return vec3f(a.x + b.x * 0.5 + c.x * 0.25, a.yz + b.yz * 1.015 + c.yz * 1.0275);\n}\n\n// A pattern's detail faded as its period falls under the pixel: 1 while `period` spans \u2265 3 px of `px` world, 0 under 1.\nfn nb_detail(period: f32, px: f32) -> f32 { return smoothstep(1.2, 3.0, period / max(px, 1e-5)); }\n\n// Coverage of a signed distance (negative inside) at a pixel of `px` world units.\nfn nb_cov(d: f32, px: f32) -> f32 { return nb_sat(0.5 - d / max(px, 1e-5)); }\n\n// A rounded box, centred, half extents `b`, radius `r`.\nfn nb_sd_box(p: vec2f, b: vec2f, r: f32) -> f32 {\n let q = abs(p) - b + vec2f(r);\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - r;\n}\n\n// A face [0, w] \xD7 [0, h] whose corners at u = w are rounded by r (the spine side square): signed distance.\nfn nb_sd_fore(q: vec2f, w: f32, h: f32, r: f32) -> f32 {\n // mirror the square side out of reach: extend the box past u = 0 by r\n let c = vec2f((w - r) * 0.5, h * 0.5);\n let b = vec2f((w + r) * 0.5, h * 0.5);\n return nb_sd_box(q - c, b, r);\n}\n\n// ---- the shadow: PCSS on the book's own map (the blocker's gap \u2192 the desk's penumbra law)\n// The golden angle as a rotation: a Vogel spiral's next direction is this times the last.\nconst NB_GOLDEN = mat2x2f(-0.7373688, 0.6754903, -0.6754903, -0.7373688);\n\nfn nb_shadow(k: NbUniforms, B: NbBook, P: vec3f, n: vec3f, rot: f32, tex: texture_depth_2d_array, cmp: sampler_comparison) -> f32 {\n if (B.sh.z < 0.5 || (u32(k.ring.y) & 1u) != 0u) { return 1.0; }\n let texel = B.sh.x;\n let res = k.shadow2.z;\n let layer = i32(B.lamp.w);\n let Po = P + n * texel * 1.5;\n let lc = B.light * vec4f(Po, 1.0);\n let uv = vec2f(lc.x * 0.5 + 0.5, 0.5 - lc.y * 0.5);\n if (uv.x <= 0.0 || uv.y <= 0.0 || uv.x >= 1.0 || uv.y >= 1.0) { return 1.0; }\n // nothing was drawn at 1 (the clear): a receiver compares just inside it, so \"no caster here\" is lit wherever it lies\n let depth = min(lc.z, 0.99995);\n let bias = 0.35 / max(B.sh.y, 1.0);\n // the blocker search: as far as the widest penumbra this book can cast\n let widest = k.shadow.x + k.shadow.y * B.sh.w;\n let search = clamp(2.2 * widest / texel, 2.0, 72.0);\n var sum = 0.0;\n var cnt = 0.0;\n let base = uv * res;\n var dir = vec2f(cos(rot), sin(rot));\n for (var i = 0; i < 10; i++) {\n let o = dir * sqrt((f32(i) + 0.5) / 10.0) * search;\n dir = NB_GOLDEN * dir;\n let t = clamp(vec2i(base + o), vec2i(0), vec2i(i32(res) - 1));\n let d = textureLoad(tex, t, layer, 0);\n if (d < depth - bias) { sum += d; cnt += 1.0; }\n }\n if (cnt < 0.5) { return 1.0; }\n let gap = (depth - sum / cnt) * B.sh.y * B.lamp.z; // the blocker's height over the receiver, world units\n let sigma = k.shadow.x + k.shadow.y * max(gap, 0.0);\n let r = clamp(sigma / texel * 1.7, 0.75, 72.0);\n var lit = 0.0;\n var wsum = 0.0;\n dir = vec2f(-dir.y, dir.x);\n let inv2s2 = 1.0 / (2.0 * (r / 1.7) * (r / 1.7) + 1e-4);\n for (var i = 0; i < 14; i++) {\n let o = dir * sqrt((f32(i) + 0.5) / 14.0) * r;\n dir = NB_GOLDEN * dir;\n let w = exp(-dot(o, o) * inv2s2);\n lit += w * textureSampleCompareLevel(tex, cmp, (base + o) / res, layer, depth - bias);\n wsum += w;\n }\n return lit / max(wsum, 1e-5);\n}\n\n// ---- the colour chain: the lamp's light on a face, then the desk's day or night\nfn nb_colour(u: MatUniforms, albedo: vec3f, light: f32, gobo: f32, noise: f32) -> vec3f {\n let day = shade_mat(u, clamp(albedo * pow(max(light, 0.0), 1.0 / 2.2), vec3f(0.0), vec3f(1.0)), gobo);\n if (u.night.x <= 0.0) { return day; }\n let night = night_mat(u, srgb_to_linear(clamp(albedo, vec3f(0.0), vec3f(1.0))) * max(light, 0.0), gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(day, night, u.night.x);\n}\n\n// ---- the cloth: a plain weave as a height, with its threads' own shades\nstruct NbCloth { h: f32, g: vec2f, shade: f32 }\nfn nb_cloth(q: vec2f, pitch: f32, px: f32) -> NbCloth {\n let fade = 1.0 - smoothstep(0.35, 0.9, px / pitch);\n let a = q / pitch;\n let cell = floor(a);\n let over = ((i32(cell.x) + i32(cell.y)) & 1) == 0;\n let f = fract(a) - vec2f(0.5);\n // warp (along v) over weft (along u) on alternate cells: a raised thread across each cell\n let warp = 1.0 - 4.0 * f.x * f.x;\n let weft = 1.0 - 4.0 * f.y * f.y;\n let h = select(weft, warp, over) * 0.8 + 0.2 * (warp * weft);\n let dwarp = -8.0 * f.x / pitch;\n let dweft = -8.0 * f.y / pitch;\n let g = select(vec2f(0.0, dweft), vec2f(dwarp, 0.0), over) * 0.8 + 0.2 * vec2f(dwarp * weft, warp * dweft);\n // the threads' slubs: each thread a little thicker or lighter along its run\n let slub = nb_vn(vec2f(select(cell.y, cell.x, over) * 1.7, select(a.x, a.y, over) * 0.21)).x;\n var out: NbCloth;\n out.h = (h - 0.55 + slub * 0.35) * fade;\n out.g = g * fade;\n out.shade = (slub * 0.9 + (h - 0.55) * 0.35) * fade;\n return out;\n}\n\n// ---- the cover's DESIGNS, printed on the cloth past the spine band (u, v in the face's world units from the band's edge and the head)\nfn nb_ring(d: f32, r: f32, w: f32, px: f32) -> f32 { return nb_cov(abs(d - r) - w * 0.5, px); }\n\nfn nb_hash2(p: vec2f) -> f32 { return hash12(p * vec2f(0.1031, 0.1030) + vec2f(19.19, 7.7)); }\n\n// 0 plain cloth \xB7 1 orbit (rings round a brass sun) \xB7 2 tiles (a grid of quarter-rounds, halves and dots) \xB7 3 label (a cream label on kraft) \xB7 4 bordered (a blind-pressed frame)\nfn nb_design(B: NbBook, q: vec2f, w: f32, h: f32, px: f32, base: vec3f) -> vec4f {\n let id = i32(B.look.x + 0.5);\n var c = base;\n var press = 0.0; // a blind line pressed into the cloth (a height)\n if (id == 1) {\n let o = vec2f(w * 0.5, h * 0.47);\n let d = length(q - o);\n let ringW = 0.9;\n for (var i = 1; i <= 6; i++) {\n let r = f32(i) * min(w, h) * 0.075 + 4.0;\n c = mix(c, B.col3.rgb, nb_ring(d, r, ringW, px) * 0.8);\n // a planet on each ring\n let a = nb_hash2(vec2f(f32(i), 3.0)) * 6.2831 + B.look.w;\n let pc = o + vec2f(cos(a), sin(a)) * r;\n let pr = 1.6 + 2.4 * nb_hash2(vec2f(f32(i), 9.0));\n let pcol = select(select(B.col4.rgb, B.col2.rgb, i % 3 == 1), B.col3.rgb, i % 3 == 2);\n c = mix(c, pcol, nb_cov(length(q - pc) - pr, px));\n }\n c = mix(c, B.col2.rgb, nb_cov(d - 9.0, px));\n } else if (id == 2) {\n let n = vec2f(3.0, 4.0);\n let cs = vec2f(w, h) / n;\n let cell = clamp(floor(q / cs), vec2f(0.0), n - vec2f(1.0));\n let f = (q - cell * cs) / cs; // 0..1 in the tile\n let r = nb_hash2(cell + vec2f(B.look.w * 7.0, 1.0));\n let r2 = nb_hash2(cell + vec2f(5.0, B.look.w * 3.0));\n let bgI = i32(r2 * 4.0);\n let fgI = (bgI + 1 + i32(r * 3.0)) % 4;\n var bg = B.col2.rgb; var fg = B.col3.rgb;\n if (bgI == 1) { bg = B.col3.rgb; } else if (bgI == 2) { bg = B.col4.rgb; } else if (bgI == 3) { bg = base; }\n if (fgI == 0) { fg = B.col2.rgb; } else if (fgI == 2) { fg = B.col4.rgb; } else if (fgI == 3) { fg = base; }\n let pxt = px / min(cs.x, cs.y);\n var m = 0.0;\n let kind = i32(r * 5.0);\n if (kind == 0) { m = nb_cov(length(f) - 1.0, pxt); } // a quarter-round from a corner\n else if (kind == 1) { m = nb_cov(length(f - vec2f(0.5, 1.0)) - 0.5, pxt); } // a half-round on the edge\n else if (kind == 2) { m = nb_cov((f.x - f.y) * 0.7071, pxt); } // a diagonal half\n else if (kind == 3) { m = nb_cov(length(f - vec2f(0.5)) - 0.3, pxt); } // a dot\n else { m = nb_cov(max(abs(f.x - 0.5), abs(f.y - 0.5)) - 0.22, pxt); } // a square\n c = mix(bg, fg, m);\n } else if (id == 3) {\n let lo = vec2f(w * 0.16, h * 0.16);\n let hi = vec2f(w * 0.84, h * 0.33);\n let d = nb_sd_box(q - (lo + hi) * 0.5, (hi - lo) * 0.5, 3.0);\n let lab = nb_cov(d, px);\n var lc = B.col3.rgb;\n // two ruled lines on the label for a name\n let ly = (q.y - lo.y) / (hi.y - lo.y);\n let rule = max(nb_cov(abs(ly - 0.55) * (hi.y - lo.y) - 0.3, px), nb_cov(abs(ly - 0.8) * (hi.y - lo.y) - 0.3, px)) * step(lo.x + 6.0, q.x) * step(q.x, hi.x - 6.0);\n lc = mix(lc, B.col7.rgb, rule * 0.35);\n c = mix(c, lc, lab);\n } else if (id == 4) {\n let d = nb_sd_box(q - vec2f(w, h) * 0.5, vec2f(w, h) * 0.5 - vec2f(9.0), 2.5);\n press = -exp(-(d * d) / 0.6) * 0.5;\n c = c * (1.0 - exp(-(d * d) / 0.6) * 0.08);\n }\n return vec4f(c, press);\n}\n\n// ---- the paper: its albedo (fibre, mottle) and its height (tooth, cockle), in the page's world units\nfn nb_paper_seed(seed: f32) -> vec2f { return vec2f(fract(seed * 0.618034) * 97.0, fract(seed * 0.381966) * 89.0); }\n\n// The paper from its baked texture (src/notebook/paper-tex.ts): the mottle and the fibres over a 64-unit\n// tile, the tooth's slope over a 9.6-unit one, each page's tiles offset by its number; the cockle, slow\n// enough for one noise. Explicit gradients, so it may be read anywhere and filters by its mips.\nstruct NbPaperS { albedo: vec3f, g: vec2f }\nfn nb_paper(k: NbUniforms, base: vec3f, q: vec2f, dqdx: vec2f, dqdy: vec2f, seed: f32, tex: texture_2d<f32>, samp: sampler) -> NbPaperS {\n let s = nb_paper_seed(seed);\n let a = textureSampleGrad(tex, samp, q / 64.0 + s / 64.0, dqdx / 64.0, dqdy / 64.0);\n let tt = 9.6;\n let b = textureSampleGrad(tex, samp, q / tt + s.yx / 31.0, dqdx / tt, dqdy / tt);\n let v = (a.r - 0.5) * k.paper.y * 2.0 + (a.g - 0.5) * k.paper.x * 3.0;\n var out: NbPaperS;\n out.albedo = base + vec3f(v * 1.05, v, v * 0.8);\n let tooth = (b.ba - vec2f(0.5)) * (2.0 * 48.0 / tt) * (k.paper.z * 0.02);\n let c = nb_vn(q / 55.0 + s * 0.5);\n out.g = tooth + c.yz / 55.0 * (k.paper.w * 2.0);\n return out;\n}\n\n// The ruling printed on a page: dots on a pitch, inside a margin, faded as they fall under the pixel.\nfn nb_ruling(k: NbUniforms, B: NbBook, q: vec2f, w: f32, h: f32, px: f32) -> f32 {\n let kind = i32(B.look.y + 0.5);\n if (kind == 0) { return 0.0; }\n let pitch = k.rule.x;\n let m = k.rule.z;\n let inside = step(m, q.x) * step(q.x, w - m) * step(m, q.y) * step(q.y, h - m);\n let vis = smoothstep(2.0, 5.0, pitch / px);\n // the grid is laid from the page's head and fore-edge so it sits the same on every page\n let g = (q - vec2f(w, 0.0)) / pitch;\n let f = fract(g) - vec2f(0.5);\n if (kind == 1) {\n let d = length(f) * pitch - k.rule.y;\n return nb_cov(d, max(px, 0.35)) * inside * vis;\n }\n if (kind == 2) {\n let d = abs(f.y) * pitch - k.rule.y * 0.45;\n return nb_cov(d, max(px, 0.3)) * inside * vis * 0.75;\n }\n let d = min(abs(f.x), abs(f.y)) * pitch - k.rule.y * 0.4;\n return nb_cov(d, max(px, 0.3)) * inside * vis * 0.6;\n}\n\n// ---- the case: the cover's face (u from the spine edge, v from the head, world units)\n\n// Its height's slope (d/du, d/dv): the cloth rolling over the board's edge (the bevel), the groove\n// where the band's edge lies under the cover's cloth, the hinge's pressed groove, the weave, a blind press.\nfn nb_cover_g(k: NbUniforms, B: NbBook, q: vec2f, px: f32) -> vec2f {\n let w = B.size.x;\n let h = B.size.y;\n let e = 0.05;\n let d = nb_sd_fore(q, w, h, B.size.w);\n let dg = vec2f(nb_sd_fore(q + vec2f(e, 0.0), w, h, B.size.w) - d, nb_sd_fore(q + vec2f(0.0, e), w, h, B.size.w) - d) / e;\n // bevel: z = \u22121.1\xB7(1 + d/2.2)\xB2 over the last 2.2 units \u2014 its slope along the outline's normal\n let t = nb_sat(1.0 + d / 2.2);\n var g = dg * (-2.2 * t / 2.2) * select(0.0, 1.0, d > -2.2 && d < 0.0);\n let band = B.page.w;\n let xb = q.x - band;\n g.x += 0.22 * exp(-(xb * xb) / 0.5) * (2.0 * xb / 0.5);\n let xh = q.x - 7.0;\n g.x += 0.55 * exp(-(xh * xh) / 2.2) * (2.0 * xh / 2.2);\n if (B.foot.y < 0.5) { g += nb_cloth(q, k.cloth.x, px).g * k.cloth.y; }\n else { g += nb_fbm(q * 1.1).yz * 1.1 * 0.05 * nb_detail(1.0, px); }\n if (i32(B.look.x + 0.5) == 4 && q.x > band) {\n // the blind-pressed frame\n let p = q - vec2f(band, 0.0);\n let hw = vec2f(w - band, h) * 0.5;\n let db = nb_sd_box(p - hw, hw - vec2f(9.0), 2.5);\n let dbg = vec2f(nb_sd_box(p + vec2f(e, 0.0) - hw, hw - vec2f(9.0), 2.5) - db, nb_sd_box(p + vec2f(0.0, e) - hw, hw - vec2f(9.0), 2.5) - db) / e;\n g += dbg * (0.5 * exp(-(db * db) / 0.6) * (2.0 * db / 0.6));\n }\n return g;\n}\n\nfn nb_cover_albedo(k: NbUniforms, B: NbBook, q: vec2f, px: f32) -> vec3f {\n let w = B.size.x;\n let h = B.size.y;\n let band = B.page.w;\n let design = nb_design(B, q - vec2f(band, 0.0), w - band, h, px, B.col0.rgb).rgb;\n var c = mix(design, B.col1.rgb, nb_cov(q.x - band, px));\n if (B.foot.y < 0.5 || q.x < band) { c *= 1.0 + nb_cloth(q, k.cloth.x, px).shade * 0.16; }\n else { c += vec3f(nb_fbm(q * 0.35).x * 0.035 + nb_fbm(q * 2.1).x * 0.02 * nb_detail(0.6, px)); }\n return c;\n}\n\n// The inside of a board: the endpaper, framed by the cloth's turn-in along the head, the fore-edge and the tail.\nstruct NbEnd { albedo: vec3f, ao: f32 }\nfn nb_endpaper(k: NbUniforms, B: NbBook, q: vec2f, px: f32, stacked: bool, dqdx: vec2f, dqdy: vec2f, tex: texture_2d<f32>, samp: sampler) -> NbEnd {\n let w = B.size.x;\n let h = B.size.y;\n let turn = 4.2;\n let de = nb_sd_fore(q - vec2f(-2.0, turn), w - turn + 2.0, h - 2.0 * turn, max(B.size.w - turn * 0.6, 1.0));\n let inside = nb_cov(de, px);\n var cloth = B.col0.rgb;\n if (B.foot.y < 0.5) { cloth *= 1.0 + nb_cloth(q, k.cloth.x, px).shade * 0.16; }\n let paper = nb_paper(k, B.col5.rgb, q, dqdx, dqdy, 91.0 + B.look.w, tex, samp).albedo;\n var out: NbEnd;\n out.albedo = mix(cloth, paper, inside);\n // the endpaper's edge: a hairline of shade where the paper lies over the cloth\n out.ao = 1.0 - 0.3 * exp(-(de * de) / 0.35) * (1.0 - inside * 0.4);\n if (stacked) {\n // the text block standing on it: occlusion at its foot\n let sq = B.open.w;\n let ds = nb_sd_fore(q - vec2f(-4.0, sq), w - sq + 4.0, h - 2.0 * sq, B.page.z);\n out.ao *= 1.0 - 0.34 * exp(-max(ds, 0.0) / 2.6);\n }\n return out;\n}\n\n// A board's edge: the cloth wrapped over it \u2014 the band's near the spine, the cover's beyond.\nfn nb_board(k: NbUniforms, B: NbBook, q: vec2f, px: f32) -> vec3f {\n let w = B.size.x;\n let h = B.size.y;\n let r = B.size.w;\n let total = 2.0 * (w - r) + NB_PI * r + h - 2.0 * r;\n let band = B.page.w;\n let inBand = max(1.0 - smoothstep(band - 0.5, band + 0.5, q.x), smoothstep(total - band - 0.5, total - band + 0.5, q.x));\n var c = mix(B.col0.rgb, B.col1.rgb, inBand);\n c *= 0.94 + nb_cloth(vec2f(q.x, q.y * 3.0), k.cloth.x, px).shade * 0.12;\n return c;\n}\n\n// A stack's edge: the sheets' edges, one stripe each, each sheet a shade of its own; averaged as they fall under the pixel.\nfn nb_edge(k: NbUniforms, B: NbBook, q: vec2f, sheets: f32, side: f32, px: f32) -> vec3f {\n let t = max(B.foot.z, 1e-3);\n let f = q.y / t;\n let i = floor(f);\n let g = fract(f);\n let vis = smoothstep(0.7, 2.2, t / px);\n let own = hash12(vec2f(i + side * 131.0, 7.0)) - 0.5;\n let gap = pow(1.0 - min(g, 1.0 - g) * 2.0, 6.0);\n let rough = nb_vn(vec2f(q.x * 0.35, i * 1.7)).x * 0.05;\n let shade = 0.9 + (own * 0.08 + rough) * vis - gap * 0.3 * vis;\n return B.col6.rgb * shade;\n}\n",
|
|
775
|
+
"kit/composite.wgsl": "// (The kit's since K4a \u2014 moved from notebook/notebook-composite.wgsl: every layered kind lays its layer with it, kit/layer.ts `layerComposite`.)\n// A layered kind's layer onto the canvas: the resolved, premultiplied layer, over what the ground drew \u2014 through the slot's PORTAL CHAIN\n// (design-018 \xA74 \u2014 the kit's contract, which the kind's own passes cannot keep: they draw into the layer, not onto the canvas). The\n// whole layer is laid at the chain's cover, so its object fades as ONE (a book's cover never shows its pages through it \u2014 group\n// opacity, the tray's feather); at the root the chain is empty, the cover exactly 1, and the layer is laid as it always was.\n// K7a: the layer is its screen BOX's (kit/layer.ts `BoxTargets`) \u2014 the texel of a pixel is the pixel less the box's origin.\n\n@group(0) @binding(0) var layer: texture_2d<f32>;\n@group(0) @binding(1) var<uniform> origin: vec4f;\n@group(0) @binding(2) var<uniform> u: MatUniforms;\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f {\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n return vec4f(p[vid], 0.0, 1.0);\n}\n\n@fragment\nfn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n let dpr = u.cam.w;\n return textureLoad(layer, vec2i(pos.xy) - vec2i(origin.xy), 0) * portal_cover(pos.xy / dpr, u.portals, u.clips, dpr);\n}\n",
|
|
776
|
+
"marks/marks-pass.wgsl": "// The marks pass \u2014 the desk's chrome over every stratum (design-015 \xA74.2 stratum 5, \xA77; D4a): one\n// instanced draw, one quad per mark over the screen rect the CPU gave it (`quad`, CSS px \u2014 the marks do\n// not zoom), one `marks_shade` per covered fragment, premultiplied out; a mark of light returns alpha 0,\n// so the blend (one, one \u2212 src alpha) adds it. The glyphs are the rulers' mono atlas (RULER.md), the\n// mat's own texture \u2014 the pills' and the rulers' numerals are one face.\n\n@group(0) @binding(0) var<uniform> u: MarksUniforms;\n@group(0) @binding(1) var<storage, read> marks: array<Mark>;\n@group(0) @binding(2) var glyph_tex: texture_2d<f32>;\n@group(0) @binding(3) var glyph_samp: sampler;\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\nconst CORNERS = array<vec2f, 6>(\n vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0),\n vec2f(0.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 1.0),\n);\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VSOut {\n var out: VSOut;\n out.idx = iid;\n let q = marks[iid].quad;\n let pos = mix(q.xy, q.zw, CORNERS[vid]); // CSS px\n out.clip = vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let dpr = max(u.view.z, 1e-6);\n var c = marks_shade(marks[in.idx], in.clip.xy / dpr, dpr, u.atlas, glyph_tex, glyph_samp);\n // the tray's name tags fade in under the drawer's header as its specimens do (design-018 \xA74, R4) \u2014 from the first tag's record on\n if (u.fade.y > 0.0 && f32(in.idx) >= u.fade.z) { c *= smoothstep(u.fade.x, u.fade.x + u.fade.y, in.clip.y / dpr); }\n if (c.a <= 0.0 && max(c.r, max(c.g, c.b)) <= 0.0) { discard; }\n return c;\n}\n",
|
|
777
|
+
"marks/marks.wgsl": "// The MARKS \u2014 the desk's chrome as SDF terms (design-015 \xA77, stratum 5; D4a): *Marks on the Mat*'s\n// reference drawing code (vibe-field/draft/desk-chrome/chrome.js) as distances. Every mark is ONE\n// record (a `Mark`, laid out on the CPU by src/marks/layout.ts in SCREEN px \u2014 the chrome keeps its size\n// at every zoom), shaded here into a premultiplied colour: an outline or a fill of a turned rounded\n// box (a frame, a ring, a knob, the vellum, a pill), the four brackets of a frame, a segment (solid or\n// dotted, a laser's line), a gap's bar with its end ticks, a flare, a glyph of the rulers' mono atlas\n// (with its halo), a strip of masking tape. Light is ADDED: a mark flagged so returns alpha 0 over its\n// colour, which the pass's premultiplied blend turns into dst + src \u2014 Canvas2D's \"lighter\".\n// Pure: every resource is a parameter (engine/shader.ts's rule).\n\nconst MARK_STROKE: f32 = 0.0;\nconst MARK_FILL: f32 = 1.0;\nconst MARK_BRACKETS: f32 = 2.0;\nconst MARK_SEGMENT: f32 = 3.0;\nconst MARK_BAR: f32 = 4.0;\nconst MARK_FLARE: f32 = 5.0;\nconst MARK_GLYPH: f32 = 6.0;\nconst MARK_TAPE: f32 = 7.0;\n\n/** Coverage of a distance (CSS px) over one DEVICE px of ramp. */\nfn marks_cov(sd: f32, dpr: f32) -> f32 { return clamp(0.5 - sd * dpr, 0.0, 1.0); }\n\n/** A screen point in a mark's own frame: about its centre, turned by \u2212angle (positive turns clockwise on screen, y down). */\nfn marks_local(p: vec2f, c: vec2f, angle: f32) -> vec2f {\n let d = p - c;\n let cs = cos(angle);\n let sn = sin(angle);\n return vec2f(cs * d.x + sn * d.y, -sn * d.x + cs * d.y);\n}\n\n/**\n * The distance to the four BRACKETS of a rounded frame (half extents X, Y, corner r), each reaching L from its\n * corner along both sides (chrome.js `bracketsPath`): folded into one quadrant, the run along the top edge from\n * its end to the arc, the arc, and the run down the side \u2014 round caps come from the distance itself.\n */\nfn marks_brackets(p: vec2f, X: f32, Y: f32, r: f32, L: f32) -> f32 {\n let q = abs(p);\n let top = length(q - vec2f(clamp(q.x, X - L, X - r), Y));\n let side = length(q - vec2f(X, clamp(q.y, Y - L, Y - r)));\n var d = min(top, side);\n let v = q - vec2f(X - r, Y - r);\n if (r > 0.0 && v.x >= 0.0 && v.y >= 0.0) { d = min(d, abs(length(v) - r)); }\n return d;\n}\n\n/** The distance to a dotted line A\u2192B: a dot every `period` from A (Canvas2D's `[0.01, period \u2212 0.01]` dash with round caps). */\nfn marks_dotted(p: vec2f, a: vec2f, b: vec2f, period: f32) -> f32 {\n let ab = b - a;\n let len = length(ab);\n let dir = ab / max(len, 1e-6);\n let k = clamp(round(dot(p - a, dir) / period), 0.0, floor(len / period));\n return length(p - (a + dir * (k * period)));\n}\n\n/** A gap's bar: the segment A\u2192B and a tick `tick` either side of it at each end, one stroke. */\nfn marks_bar(p: vec2f, a: vec2f, b: vec2f, tick: f32) -> f32 {\n let ab = b - a;\n let n = vec2f(-ab.y, ab.x) / max(length(ab), 1e-6) * tick;\n return min(sdf_segment(p, a, b), min(sdf_segment(p, a - n, a + n), sdf_segment(p, b - n, b + n)));\n}\n\n/** Masking tape's torn outline (desk.css `.obj-tape i` clip-path), unit square, clockwise from the top-left. */\nconst TAPE_EDGE = array<vec2f, 12>(\n vec2f(0.03, 0.0), vec2f(0.97, 0.04), vec2f(1.0, 0.22), vec2f(0.96, 0.40), vec2f(1.0, 0.60), vec2f(0.97, 0.80),\n vec2f(1.0, 1.0), vec2f(0.02, 0.96), vec2f(0.0, 0.78), vec2f(0.04, 0.58), vec2f(0.0, 0.40), vec2f(0.03, 0.20),\n);\n\n/** The signed distance to the tape's outline, the strip `half` in its own frame (Quilez's polygon). */\nfn marks_tape_sd(p: vec2f, half: vec2f) -> f32 {\n let v0 = (TAPE_EDGE[0] - 0.5) * 2.0 * half;\n var d = dot(p - v0, p - v0);\n var s = 1.0;\n for (var i = 0u; i < 12u; i++) {\n let j = (i + 11u) % 12u;\n let vi = (TAPE_EDGE[i] - 0.5) * 2.0 * half;\n let vj = (TAPE_EDGE[j] - 0.5) * 2.0 * half;\n let e = vj - vi;\n let w = p - vi;\n let b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);\n d = min(d, dot(b, b));\n let c0 = p.y >= vi.y;\n let c1 = p.y < vj.y;\n let c2 = e.x * w.y > e.y * w.x;\n if ((c0 && c1 && c2) || (!c0 && !c1 && !c2)) { s = -s; }\n }\n return s * sqrt(d);\n}\n\n/** CSS `saturate(s) brightness(b)` on an sRGB colour (the Filter Effects matrix). */\nfn marks_moonlit(c: vec3f, s: f32, b: f32) -> vec3f {\n let r = vec3f(0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s);\n let g = vec3f(0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s);\n let bl = vec3f(0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s);\n return clamp(vec3f(dot(r, c), dot(g, c), dot(bl, c)) * b, vec3f(0.0), vec3f(1.0));\n}\n\n/** One texel sample of a cell (texels from its top-left, kept inside the cell: a neighbour's glyph never bleeds in). */\nfn marks_glyph_at(t: vec2f, cell: f32, size: vec2f, atlas: vec4f, tex: texture_2d<f32>, samp: sampler) -> f32 {\n let at = vec2f(cell * atlas.w, 0.0) + clamp(t, vec2f(0.5), size - 0.5);\n return textureSampleLevel(tex, samp, at / atlas.xy, 0.0).r;\n}\n\n/** The glyph atlas's coverage at a point of a cell's own frame (CSS px from its top-left; the cell `size` CSS px), `halo` > 0 = its coverage dilated that far. */\nfn marks_glyph_cov(q: vec2f, cell: f32, size: vec2f, atlas: vec4f, halo: f32, tex: texture_2d<f32>, samp: sampler) -> f32 {\n let scale = atlas.z;\n let t = q * scale;\n let s = size * scale;\n var c = marks_glyph_at(t, cell, s, atlas, tex, samp);\n if (halo > 0.0) {\n for (var i = 0; i < 8; i++) {\n let a = f32(i) * 0.785398163;\n let o = vec2f(cos(a), sin(a)) * halo * scale;\n c = max(c, marks_glyph_at(t + o, cell, s, atlas, tex, samp));\n c = max(c, marks_glyph_at(t + o * 0.5, cell, s, atlas, tex, samp));\n }\n }\n return c;\n}\n\n/**\n * One mark at screen point `p` (CSS px), premultiplied \u2014 alpha 0 when it is light (added). `atlas` = the glyph atlas's\n * width, height (texels), texels per CSS px and cell width (texels).\n */\nfn marks_shade(M: Mark, p: vec2f, dpr: f32, atlas: vec4f, tex: texture_2d<f32>, samp: sampler) -> vec4f {\n let kind = M.shape.x;\n let width = M.shape.z;\n var cov = 0.0;\n var col = M.colour;\n if (kind == MARK_STROKE || kind == MARK_FILL || kind == MARK_BRACKETS) {\n let q = marks_local(p, M.centre.xy, M.centre.z);\n let sd = sdf_round_box(q, M.half.xy, M.centre.w);\n if (kind == MARK_STROKE) {\n cov = marks_cov(abs(sd) - 0.5 * width, dpr);\n } else if (kind == MARK_FILL) {\n cov = marks_cov(sd, dpr);\n } else {\n cov = marks_cov(marks_brackets(q, M.half.x, M.half.y, M.centre.w, M.shape.w) - 0.5 * width, dpr);\n }\n } else if (kind == MARK_SEGMENT) {\n let d = select(sdf_segment(p, M.centre.xy, M.half.xy), marks_dotted(p, M.centre.xy, M.half.xy, M.aux.x), M.aux.x > 0.0);\n cov = marks_cov(d - 0.5 * width, dpr);\n } else if (kind == MARK_BAR) {\n cov = marks_cov(marks_bar(p, M.centre.xy, M.half.xy, M.aux.x) - 0.5 * width, dpr);\n } else if (kind == MARK_FLARE) {\n // a radial gradient: the core at the centre, the laser at `stop`, nothing at the rim \u2014 interpolated premultiplied\n let t = length(p - M.centre.xy) / M.centre.w;\n if (t >= 1.0) { return vec4f(0.0); }\n let core = vec4f(M.aux.rgb * M.aux.a, M.aux.a);\n let mid = vec4f(M.colour.rgb * M.colour.a, M.colour.a);\n let stop = M.half.x;\n let c = select(mix(mid, vec4f(0.0), (t - stop) / (1.0 - stop)), mix(core, mid, t / stop), t < stop);\n return vec4f(c.rgb, select(c.a, 0.0, (u32(M.shape.y) & 1u) != 0u));\n } else if (kind == MARK_GLYPH) {\n // the cell turned about its centre (`centre.xy`), its own frame from its top-left; a halo reaches `aux.y` past it\n let q = marks_local(p, M.centre.xy, M.centre.z) + 0.5 * M.half.xy;\n let h = M.aux.y;\n if (q.x < -h || q.y < -h || q.x > M.half.x + h || q.y > M.half.y + h) { return vec4f(0.0); }\n cov = marks_glyph_cov(q, M.aux.x, M.half.xy, atlas, h, tex, samp);\n } else if (kind == MARK_TAPE) {\n // the strip in its own frame: its torn outline, its fibres (1 in `every` object units), a light from above, moonlit by night\n let q = marks_local(p, M.centre.xy, M.centre.z);\n let half = M.half.xy;\n cov = marks_cov(marks_tape_sd(q, half), dpr);\n let uv = (q + half) / (2.0 * half);\n let units = M.aux.x; // CSS px per object unit\n let sheen = mix(vec4f(1.0, 1.0, 1.0, M.half.z), vec4f(0.0, 0.0, 0.0, M.half.w), clamp(uv.y, 0.0, 1.0));\n var c = vec4f(M.colour.rgb * M.colour.a, M.colour.a);\n c = vec4f(sheen.rgb * sheen.a, sheen.a) + c * (1.0 - sheen.a);\n let every = M.shape.w;\n let x = (q.x + half.x) / max(units, 1e-6);\n let seen = clamp(every * units * dpr / 3.0 - 0.5, 0.0, 1.0); // fibres under ~1.5 device px each fade to the mean\n let fibre = select(0.0, 1.0, fract(x / every) < 1.0 / every) * seen + (1.0 - seen) / every;\n let fa = M.aux.w * fibre;\n c = vec4f(vec3f(fa), fa) + c * (1.0 - fa);\n let rgb = marks_moonlit(c.rgb / max(c.a, 1e-6), M.aux.y, M.aux.z);\n let a = c.a * cov * M.centre.w;\n return vec4f(rgb * a, a);\n }\n let a = col.a * cov;\n return vec4f(col.rgb * a, select(a, 0.0, (u32(M.shape.y) & 1u) != 0u));\n}\n",
|
|
778
|
+
"mat/mat-pass.wgsl": "// PASS 2, the cutting mat \u2014 one fullscreen triangle: the lattice's lines as\n// analytic coverage, the grain, the gobo projected onto the desk plane, the\n// reference's grade by day or the night's eye by night (MAT.md). Alpha 1 at\n// rest (a plain write); a flight fades it and clips it to the portal.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var gobo_tex: texture_2d<f32>;\n@group(0) @binding(2) var gobo_samp: sampler;\n@group(0) @binding(3) var noise_tex: texture_2d<f32>;\n@group(0) @binding(4) var noise_samp: sampler;\n@group(0) @binding(5) var glyph_tex: texture_2d<f32>; // the rulers' glyph atlas (RULER.md); a 1\xD71 empty cell until a host uploads one\n\nstruct VSOut { @builtin(position) clip: vec4f }\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> VSOut {\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n var out: VSOut;\n out.clip = vec4f(p[vid], 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let zoom = mat_zoom(u);\n let dpr = mat_dpr(u);\n let frag = in.clip.xy; // device px\n let screen = frag / dpr; // CSS px\n let px = 1.0 / (zoom * dpr); // world per device px\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n // the rulers' print \u2014 screen-space ink mixed into the albedo before the light, so it is lit and shadowed like the lines\n let ruler = ruler_ink(u, screen, frag, glyph_tex, gobo_samp);\n let albedo = mat_albedo(u, u.phase.xy + screen / zoom, px, frag, bn, ruler);\n // the lamp's gobo where the LIGHT sees this pixel (MINIMAT.md \xA74): the slot's own camera at rest\n // (`light` = `cam`, so this is the desk under the pixel), the host desk's through a mini mat's face\n var gobo = sample_gobo(u, gobo_tex, gobo_samp, desk_of(u, u.light.xy + screen / max(u.light.z, 1e-12)), bn.z);\n if (u.light.w > 0.0) { gobo = mix(gobo, sample_gobo(u, gobo_tex, gobo_samp, desk_of(u, u.light2.xy + screen / max(u.light2.z, 1e-12)), bn.z), u.light.w); }\n // Straight alpha: the presentation's opacity through the portal clip \u2014 both 1 at rest,\n // when \"source over\" reduces to a plain write.\n return vec4f(mat_colour(u, albedo, gobo, bn.y), u.presence.x * portal_cover(screen, u.portals, u.clips, dpr));\n}\n",
|
|
779
|
+
"mat/mat.wgsl": "// The cutting mat's maths \u2014 a PURE module: no bindings. The uniform block and\n// the textures arrive as parameters. Every function body is the reference's\n// (research/tree-shadow/prototype/src/shaders.js), transcribed to the engine's\n// units: world = CSS px at zoom 1, the lattice's decade rungs, `MatUniforms`\n// from src/mat/layout.ts prepended by the engine.\n\nfn mat_zoom(u: MatUniforms) -> f32 { return max(u.cam.z, 1e-12); }\nfn mat_dpr(u: MatUniforms) -> f32 { return max(u.cam.w, 1.0); }\n\n// ---- the reference's noise (hash12 \xB7 valueNoise), on framebuffer pixels\nfn hash12(p: vec2f) -> f32 {\n let n = sin(dot(p, vec2f(127.1, 311.7))) * 43758.5453;\n return n - floor(n);\n}\n\nfn value_noise(p: vec2f) -> f32 {\n let i = floor(p);\n var f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash12(i), hash12(i + vec2f(1.0, 0.0)), f.x),\n mix(hash12(i + vec2f(0.0, 1.0)), hash12(i + vec2f(1.0, 1.0)), f.x),\n f.y);\n}\n\nfn srgb_to_linear(c: vec3f) -> vec3f {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3f(2.4)), step(vec3f(0.04045), c));\n}\n\nfn rgb2hsb(c: vec3f) -> vec3f {\n let K = vec4f(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n let p = mix(vec4f(c.bg, K.wz), vec4f(c.gb, K.xy), step(c.b, c.g));\n let q = mix(vec4f(p.xyw, c.r), vec4f(c.r, p.yzx), step(p.x, c.r));\n let d = q.x - min(q.w, q.y);\n let e = 1.0e-10;\n return vec3f(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n}\n\nfn hsb2rgb(c: vec3f) -> vec3f {\n let K = vec4f(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n let p = abs(fract(K.xyz + c.x) * 6.0 - K.w);\n return c.z * mix(K.xxx, clamp(p - K.x, vec3f(0.0), vec3f(1.0)), c.y);\n}\n\n// ---- the lines. `px` is world units per DEVICE pixel; half-widths are in device px.\nfn line_coverage(x: f32, spacing: f32, half_width: f32, px: f32) -> f32 {\n let d = abs(fract(x / spacing - 0.5) - 0.5) * spacing;\n return 1.0 - smoothstep(max(half_width - px, 0.0), half_width + px, d);\n}\n\nfn grid2(p: vec2f, spacing: f32, half_width: f32, px: f32) -> f32 {\n return max(line_coverage(p.x, spacing, half_width, px), line_coverage(p.y, spacing, half_width, px));\n}\n\n// One law for every rung (src/lattice/line.ts `lineWeight`): fade in over the\n// engine's window by the rung's OWN cell, then thicken and darken over the\n// next decade. x = half-width (device px), y = alpha.\nfn line_weight(u: MatUniforms, cell_px: f32) -> vec2f {\n let s = smoothstep(u.lod.x, u.lod.y, cell_px);\n let t = saturate(log(max(cell_px, 1e-9) / u.lod.y) / 2.302585093);\n return vec2f(mix(u.line.x, u.line.y, t), u.line.z * s + (u.line.w - u.line.z) * t);\n}\n\n// The mat's albedo at a point: the reference's `cuttingMatAlbedo` with the\n// three lattice rungs in place of its own decade, and the line law above in\n// place of its fade ladder. `world` is lattice-phase world (wrapped); `px` is\n// world per device pixel; `frag` is the framebuffer pixel; `ruler` the rulers'\n// print at it (ruler.wgsl) \u2014 ink over the lines, and at 0 exactly the mix below\n// returns the lined mat untouched, bit for bit.\nfn mat_albedo(u: MatUniforms, world: vec2f, px: f32, frag: vec2f, bn: vec3f, ruler: f32) -> vec3f {\n var sage = u.ground.xyz;\n let amp = u.grain.x;\n var g = (hash12(frag * vec2f(0.73, 0.91)) - 0.5) * amp;\n g += (value_noise(frag * 0.45 + vec2f(u.view.z * 0.9, -u.view.z * 0.6)) - 0.5) * amp;\n g += (bn.x - 0.5) * amp * 0.5;\n sage += vec3f(g * 0.4, g, g * 0.35);\n\n // A rung under the window has alpha 0 and no coverage to compute \u2014 the\n // fine rung at most zooms. The branches are uniform across the frame.\n let zoom = mat_zoom(u);\n var cover = 0.0;\n let wf = line_weight(u, u.rungs.x * zoom);\n if (wf.y > 0.0) { cover = grid2(world, u.rungs.x, wf.x * px, px) * wf.y; }\n let wm = line_weight(u, u.rungs.y * zoom);\n if (wm.y > 0.0) { cover = max(cover, grid2(world, u.rungs.y, wm.x * px, px) * wm.y); }\n let wc = line_weight(u, u.rungs.z * zoom);\n if (wc.y > 0.0) { cover = max(cover, grid2(world, u.rungs.z, wc.x * px, px) * wc.y); }\n return srgb_to_linear(clamp(mix(mix(sage, u.ink.xyz, cover), u.ink.xyz, ruler), vec3f(0.0), vec3f(1.0)));\n}\n\n// ---- the gobo (the reference's sampleBlur \xB7 getGoboBlurRatio \xB7 sampleGobo)\nfn linearstep(edge0: f32, edge1: f32, x: f32) -> f32 {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\n// Linearise the WebGL-range NDC z with the projector's near/far, then ramp\n// between the two depths: sharp near the projector, soft far from it.\nfn blur_ratio(ndc_z: f32, p: vec4f) -> f32 {\n return linearstep(p.x, p.y, (2.0 * p.z * p.w) / (p.w + p.z - ndc_z * (p.w - p.z)));\n}\n\n// Six taps on a golden-angle spiral, the start angle from blue noise so the\n// pattern differs per pixel (and per frame while the offset moves).\nfn sample_blur(tex: texture_2d<f32>, samp: sampler, uv: vec2f, texel: vec2f, blur: f32, n: f32) -> f32 {\n // No blur: six taps at one point are one tap. The sharp end of the desk.\n if (blur <= 0.0) { return textureSampleLevel(tex, samp, uv, 0.0).r; }\n let G = 2.39996323;\n let rot = mat2x2f(cos(G), sin(G), -sin(G), cos(G));\n let a0 = n * 6.28318530718;\n var d = vec2f(cos(a0), sin(a0));\n let s = texel * blur * (1.0 / sqrt(6.0));\n var c = 0.0;\n for (var i = 0; i < 6; i++) {\n c += textureSampleLevel(tex, samp, uv + d * sqrt(f32(i + 1)) * s, 0.0).r;\n d = rot * d;\n }\n return c / 6.0;\n}\n\n// The gobo term at a desk point (metres): 1 = lit. Outside the projector's\n// frame the plate is not consulted at all \u2014 the cone is finite, as the\n// reference's is.\nfn sample_gobo(u: MatUniforms, tex: texture_2d<f32>, samp: sampler, desk: vec3f, n: f32) -> f32 {\n if (u.gobo.x <= 0.0) { return 1.0; }\n var clip = u.goboMatrix * vec4f(desk, 1.0);\n let ndc = clip.xyz / clip.w;\n // The wind target's row 0 is the plate's top row, so v runs downward.\n let uv = vec2f(0.5 + 0.5 * ndc.x, 0.5 - 0.5 * ndc.y);\n if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return 1.0; }\n let g = sample_blur(tex, samp, uv, vec2f(1.0 / 512.0), blur_ratio(ndc.z, u.goboParams) * u.gobo.y, n);\n return mix(1.0, g, u.gobo.x);\n}\n\n// World (CSS px at zoom 1, unwrapped) \u2192 the desk plane in the projector's frame.\nfn desk_of(u: MatUniforms, world: vec2f) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w, u.plane.y + world.y * u.plane.z);\n}\n\n// ---- the LIGHT a slot is lit by (MINIMAT.md \xA74; src/mat/layout.ts `SlotLight`). The lamp's gobo is\n// sampled where a light CAMERA sees a point, not where the slot's camera does: the same camera for the\n// root at rest; the host desk's for a mini mat's inside \u2014 the dapple runs across the mini mat as across\n// anything lying on the desk \u2014 and two of them cross-faded (`light.w`) while a flight hands one lamp to\n// the other. An object's world point `p` raised `h` world units, as light camera `L` (x, y, zoom) sees it:\n// the point on screen, back into L's world; a height scales as a length does.\nfn lit_desk(u: MatUniforms, L: vec4f, p: vec2f, h: f32) -> vec3f {\n let s = max(u.cam.z, 1e-12) / max(L.z, 1e-12);\n let w = L.xy + (p - u.cam.xy) * s;\n return vec3f(u.plane.x + w.x * u.plane.z, u.plane.w + h * s * u.plane.z, u.plane.y + w.y * u.plane.z);\n}\n\n// The gobo on an object's world point `p` raised `h`: under the slot's own lamp the desk point itself,\n// `own` (the object's own `desk_at`, so a root's object reads exactly what it always read); else under\n// the light camera \u2014 then, while a flight hands it over, cross-faded to the second.\nfn lit_gobo(u: MatUniforms, tex: texture_2d<f32>, samp: sampler, p: vec2f, h: f32, own: vec3f, n: f32) -> f32 {\n var g = 0.0;\n if (all(u.light.xyz == u.cam.xyz)) { g = sample_gobo(u, tex, samp, own, n); }\n else { g = sample_gobo(u, tex, samp, lit_desk(u, u.light, p, h), n); }\n if (u.light.w > 0.0) { g = mix(g, sample_gobo(u, tex, samp, lit_desk(u, u.light2, p, h), n), u.light.w); }\n return g;\n}\n\n// The reference's grade: `shade = mix(1, floor + (1 \u2212 floor)\xB7gobo, mix)`, then\n// HSB: saturation up by (1 \u2212 shade)\xB7k, brightness \xD7 shade. The reference did\n// this on pow(albedo, 2.2) and wrote pow(1/2.2) \u2014 kept verbatim: that chain is\n// the mat's colour on screen.\nfn shade_mat(u: MatUniforms, albedo: vec3f, gobo: f32) -> vec3f {\n // Lit: shade is exactly 1, the saturation bump exactly 0, and the chain is\n // pow(pow(albedo, 2.2), 1/2.2) \u2014 the identity. Every pixel outside the\n // cone, and every lit one inside it, skips six pows and the HSB round trip.\n if (gobo >= 1.0) { return albedo; }\n let shade = mix(1.0, u.gobo.w + (1.0 - u.gobo.w) * gobo, u.gobo.z);\n var hsb = rgb2hsb(pow(albedo, vec3f(2.2)));\n hsb.y = min(hsb.y + (1.0 - shade) * u.grade.x, 1.0);\n hsb.z = hsb.z * shade;\n return pow(hsb2rgb(hsb), vec3f(1.0 / 2.2));\n}\n\n// ---- the NIGHT (MAT.md; src/mat/night.ts is the same arithmetic on the CPU):\n// the Moon in place of the Sun, and the eye that sees by it. The day's chain\n// above is untouched \u2014 `mat_colour` returns it, alone, when `night.x` is 0.\nfn night_luminance(c: vec3f) -> f32 { return dot(c, vec3f(0.2126729, 0.7151522, 0.0721750)); }\n\n// Scotopic luminance V' from XYZ \u2014 Larson, Rushmeier & Piatko 1997's regression:\n// the rods peak at 507 nm, so greens and blues keep their brightness by night and reds die.\nfn night_scotopic(c: vec3f) -> f32 {\n let X = dot(c, vec3f(0.4124564, 0.3575761, 0.1804375));\n let Y = dot(c, vec3f(0.2126729, 0.7151522, 0.0721750));\n let Z = dot(c, vec3f(0.0193339, 0.1191920, 0.9503041));\n return Y * (1.33 * (1.0 + (Y + Z) / max(X, 1e-6)) - 1.68);\n}\n\n// CIE 191:2010's mesopic weight \u2014 the cones' share of the seeing at L cd/m\xB2:\n// 0 at 0.005 (the rods alone), 1 at 5 (the cones alone).\nfn night_mesopic(L: f32) -> f32 { return saturate(0.767 + 0.3334 * log(max(L, 1e-9)) * 0.4342944819); }\n\nfn night_encode(c: vec3f) -> vec3f {\n return mix(c * 12.92, 1.055 * pow(max(c, vec3f(0.0)), vec3f(1.0 / 2.4)) - 0.055, step(vec3f(0.0031308), c));\n}\n\nfn night_mat(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32) -> vec3f {\n let shade = u.moon.w + (1.0 - u.moon.w) * gobo; // the Moon, and the sky's share where it is blocked\n let cone = albedo * u.moon.xyz * shade; // the mat under the Moon, as the partly adapted cones take it\n let rod = max(night_scotopic(cone), 0.0) / night_scotopic(vec3f(1.0)); // the rods' signal, white = 1\n let L = u.night.y * night_luminance(cone) * 0.3183098862; // cd/m\xB2 on the desk: E \xB7 \u03C1 / \u03C0\n let see = mix(rod * u.rod.xyz, cone, night_mesopic(L)); // what the eye reports\n let disp = u.eigengrau.xyz + see * u.night.z; // the dark's own grey under it, at the appearance's scale\n return night_encode(disp) + (noise - 0.5) * u.night.w; // encoded; then the rods' own noise\n}\n\n// The mat's colour on screen: the day's chain, the night, or a cross-fade of the two.\nfn mat_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32) -> vec3f {\n if (u.night.x <= 0.0) { return shade_mat(u, albedo, gobo); }\n let night = night_mat(u, albedo, gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(shade_mat(u, albedo, gobo), night, u.night.x);\n}\n",
|
|
780
|
+
"mat/ruler.wgsl": "// The RULERS printed on the mat (RULER.md) \u2014 a PURE module: no bindings. The\n// ink coverage at a fragment from the frame's lines, the ticks and the labels,\n// in screen space; the mat pass mixes it into the albedo before the light, so\n// the print is lit and shadowed like the lines. Every number is a uniform\n// `matUniformValues` fills from lattice/ruler.ts \u2014 the levels' spacings,\n// presences and tick lengths per frame; the shader only lays them out.\n//\n// Layout: the frame's outer rect one `margin` in from the viewport; the top\n// and left BANDS one `band` wide inside it, each closed by its inner line\n// (the ticks' baseline). A tick stands on the baseline at every site of every\n// level, its length the level's, its presence the level's. A label sits in\n// the band's outer half after its tick, laid out on the finest labelled\n// level's sites; the left ruler's labels are the top's rotated a quarter turn\n// clockwise (reading down the band, their tops toward the field) \u2014 James's\n// mockup. All distances are DEVICE px inside; positions arrive as CSS px.\n\nconst RULER_LEVELS = 5;\nconst GLYPH_PAD = 2.0;\n\n// A printed line's coverage across `d` device px at half-width `hw`: a 1 px AA ramp.\nfn ruler_line(d: f32, hw: f32) -> f32 { return 1.0 - smoothstep(hw - 0.5, hw + 0.5, abs(d)); }\n// Inside [a, b] \u2014 the ends of a segment and the extents of a band.\nfn ruler_in(x: f32, a: f32, b: f32) -> f32 { return step(a, x) * step(x, b); }\n\nfn ruler_digits(a: i32) -> i32 { var n = 1; var v = a; loop { if (v < 10) { break; } v = v / 10; n += 1; } return n; }\nfn ruler_pow10(p: i32) -> i32 { var r = 1; for (var i = 0; i < p; i++) { r = r * 10; } return r; }\n// The i-th digit (0 = leftmost) of `a` written with `nd` digits \u2014 leading zeros where `a` is short.\nfn ruler_digit_at(a: i32, i: i32, nd: i32) -> i32 { return (a / ruler_pow10(nd - 1 - i)) % 10; }\n\n// A label's text \u2014 lattice/ruler.ts `formatLabel`, per character: the glyph\n// index at slot `k` of `index \xB7 mult \xB7 10^exp` (0\u20139, 10 '-', 11 '.', -1 past\n// the end), and (x < 0) the character count. Plain digits, no suffix; a\n// fraction with its trailing zeros stripped, so \"1.0\" is \"1\" at every level.\nfn ruler_label(index: i32, mult: i32, exp: i32, k: i32) -> i32 {\n let M = index * mult;\n if (M == 0) { return select(-1, 0, k == 0); }\n let neg = M < 0;\n let a = abs(M);\n var i = k;\n if (neg) { if (i == 0) { return 10; } i -= 1; }\n if (exp >= 0) {\n let nd = ruler_digits(a);\n if (i < nd) { return ruler_digit_at(a, i, nd); }\n if (i < nd + exp) { return 0; }\n return -1;\n }\n let p = -exp;\n let nd = max(ruler_digits(a), p + 1);\n let intDigits = nd - p;\n var fracLen = p;\n loop { if (fracLen == 0) { break; } if ((a / ruler_pow10(p - fracLen)) % 10 != 0) { break; } fracLen -= 1; }\n if (i < intDigits) { return ruler_digit_at(a, i, nd); }\n if (i == intDigits) { return select(-1, 11, fracLen > 0); }\n let j = i - intDigits - 1;\n if (j < fracLen) { return ruler_digit_at(a, intDigits + j, nd); }\n return -1;\n}\nfn ruler_label_len(index: i32, mult: i32, exp: i32) -> i32 {\n var n = 0;\n loop { if (n >= 16) { break; } if (ruler_label(index, mult, exp, n) < 0) { break; } n += 1; }\n return n;\n}\n\n// The ticks and the label along ONE band. `along` is the fragment's CSS px\n// along the band (x for the top, y for the left), `across` its device px in\n// from the outer line; `phase` the lattice's wrapped camera coordinate along\n// it, `wraps` the wrap count, `lim` the band's extent along (CSS px: the inner\n// corner, the far frame line), `turned` the left band. `glyph` receives the\n// atlas texel to sample when the fragment lies in a label (its .z the label's\n// presence), else .z 0.\nstruct RulerHit { ink: f32, glyph: vec3f }\n\nfn ruler_band(u: MatUniforms, along: f32, across: f32, phase: f32, wraps: f32, lim: vec2f, turned: bool) -> RulerHit {\n var hit: RulerHit;\n hit.ink = 0.0; hit.glyph = vec3f(0.0);\n let dpr = mat_dpr(u); let zoom = mat_zoom(u);\n let band_dev = u.ruler.z * dpr;\n let hw = 0.5 * u.ruler.w * dpr;\n // only the band's own pixels pay for the levels and the text \u2014 the rest of the mat returns here\n if (across < -1.0 || across > band_dev + 1.0 || ruler_in(along, lim.x, lim.y) <= 0.0) { return hit; }\n let w = phase + along / zoom; // lattice-phase world along the band\n // the ticks: on the baseline (the band's inner edge), up the level's length; the coarsest level owning a site wins the max\n var tick = 0.0;\n for (var i = 0; i < RULER_LEVELS; i++) {\n let s = u.rulerSites[i].x;\n let len = u.rulerSites[i].y * dpr;\n let a = u.rulerSites[i].z;\n if (a <= 0.0 || len <= 0.0) { continue; }\n let d = (abs(fract(w / s - 0.5) - 0.5) * s) * zoom * dpr;\n tick = max(tick, ruler_line(d, hw) * ruler_in(across, band_dev - len - 0.5, band_dev + 0.5) * a);\n }\n hit.ink = tick * u.rulerAlpha.y;\n // the label: laid out on the finest labelled level's sites, the site at or before the fragment\n let lf = i32(u.rulerOrigin.z);\n if (lf < 0 || u.rulerAtlas.w <= 0.0) { return hit; }\n let s = u.rulerSites[lf].x;\n let nLocal = floor(w / s);\n let site = (nLocal * s - phase) * zoom; // CSS px along\n if (site < lim.x) { return hit; } // a site inside the corner square prints nothing\n let index = i32(wraps) * i32(u.rulerFormat[lf].z) + i32(nLocal);\n // the site's presence: the coarsest level it lies on\n var presence = u.rulerSites[lf].w;\n for (var j = RULER_LEVELS - 1; j > lf; j--) {\n let ratio = i32(u.rulerFormat[lf].z) / i32(u.rulerFormat[j].z);\n if (index % ratio == 0) { presence = u.rulerSites[j].w; break; }\n }\n if (presence <= 0.0) { return hit; }\n let mult = i32(u.rulerFormat[lf].x); let exp = i32(u.rulerFormat[lf].y);\n let texel = u.rulerText.w / dpr; // atlas texels per device px\n let adv = u.rulerGlyph.z / texel; // a glyph's advance, device px\n let origin = floor((site + u.rulerText.y) * dpr + 0.5); // the text's start, on a whole device px\n let t = along * dpr - origin;\n if (t < 0.0) { return hit; }\n let slot = i32(floor(t / adv));\n let g = ruler_label(index, mult, exp, slot);\n if (g < 0) { return hit; }\n // the glyph cell. The top band: the cap's top `top` in from the outer line, the glyph read down the band. The left band: the\n // glyph turned a quarter CLOCKWISE \u2014 read down the band still, but its baseline `top` in from the outer line and its top toward\n // the field (the mockup's \"1\" above \"0\", the foot of the \"1\" at the outer side). Reading along +y with the cell's rows along \u2212x\n // keeps the hand: a rotation, not the mirror a transpose would be.\n var v = 0.0;\n if (turned) { v = u.rulerGlyph.w - (across - u.rulerText.x * dpr) * texel; }\n else { v = (across - (u.rulerText.x * dpr - (u.rulerGlyph.w - u.rulerAtlas.z) / texel)) * texel; }\n if (v < 0.0 || v >= u.rulerGlyph.y) { return hit; }\n let uu = f32(g) * u.rulerGlyph.x + GLYPH_PAD + (t - f32(slot) * adv) * texel;\n hit.glyph = vec3f(uu, v, presence);\n return hit;\n}\n\n// The whole print: the frame's lines, the two bands' ticks, the labels'\n// texels sampled from the atlas. Returns ink coverage 0..1 at this fragment.\nfn ruler_ink(u: MatUniforms, screen: vec2f, frag: vec2f, glyph_tex: texture_2d<f32>, samp: sampler) -> f32 {\n if (u.ruler.x <= 0.0) { return 0.0; }\n let dpr = mat_dpr(u);\n let margin = u.ruler.y; let band = u.ruler.z;\n let W = u.view.x; let H = u.view.y;\n let hw = 0.5 * u.ruler.w * dpr;\n let m = margin * dpr; let inner = (margin + band) * dpr;\n let right = (W - margin) * dpr; let bottom = (H - margin) * dpr;\n // the frame: the outer rect, and the two baselines closing the bands\n var frame = 0.0;\n frame = max(frame, ruler_line(frag.x - m, hw) * ruler_in(frag.y, m - hw, bottom + hw));\n frame = max(frame, ruler_line(frag.x - right, hw) * ruler_in(frag.y, m - hw, bottom + hw));\n frame = max(frame, ruler_line(frag.y - m, hw) * ruler_in(frag.x, m - hw, right + hw));\n frame = max(frame, ruler_line(frag.y - bottom, hw) * ruler_in(frag.x, m - hw, right + hw));\n frame = max(frame, ruler_line(frag.x - inner, hw) * ruler_in(frag.y, m, bottom));\n frame = max(frame, ruler_line(frag.y - inner, hw) * ruler_in(frag.x, m, right));\n var ink = frame * u.rulerAlpha.x;\n // the top band (across = down from the outer line) and the left band (across = right from it); a pixel in neither pays nothing more.\n // The labels: the top band's cell read along x, the left band's along y and turned \u2014 the same atlas texels either way.\n if (ruler_in(frag.y, m - 1.0, inner + 1.0) * ruler_in(frag.x, inner - 1.0, right + 1.0) > 0.0) {\n let top = ruler_band(u, screen.x, frag.y - m, u.phase.x, u.rulerOrigin.x, vec2f(margin + band, W - margin), false);\n ink = max(ink, top.ink);\n if (top.glyph.z > 0.0) { ink = max(ink, textureSampleLevel(glyph_tex, samp, top.glyph.xy / u.rulerAtlas.xy, 0.0).r * top.glyph.z * u.rulerAlpha.z); }\n }\n if (ruler_in(frag.x, m - 1.0, inner + 1.0) * ruler_in(frag.y, inner - 1.0, bottom + 1.0) > 0.0) {\n let left = ruler_band(u, screen.y, frag.x - m, u.phase.y, u.rulerOrigin.y, vec2f(margin + band, H - margin), true);\n ink = max(ink, left.ink);\n if (left.glyph.z > 0.0) { ink = max(ink, textureSampleLevel(glyph_tex, samp, left.glyph.xy / u.rulerAtlas.xy, 0.0).r * left.glyph.z * u.rulerAlpha.z); }\n }\n return ink;\n}\n",
|
|
781
|
+
"mat/wind.wgsl": "// The wind pass \u2014 the reference's `goboWind`: one fragment per plate texel,\n// R silhouette + three sines whose amplitudes and phase the plate's other\n// channels carry, written to an r8 target the mat pass samples. Runs only when\n// the gobo time moved; the target is 512\xB2 like the plate.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var plate: texture_2d<f32>;\n\nstruct VSOut { @builtin(position) clip: vec4f }\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> VSOut {\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n var out: VSOut;\n out.clip = vec4f(p[vid], 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let params = textureLoad(plate, vec2i(in.clip.xy), 0);\n let time = u.wind.x;\n let strength = u.wind.y;\n let s = params.r;\n let amp1 = params.g * strength;\n let phase1 = (params.b - 0.5) * 6.283185;\n let amp23 = params.a * strength;\n let waves = sin(time * vec3f(1.0, 2.0, 3.0) + phase1 * vec3f(1.0, 2.0, 0.5));\n let motion = (amp1 * waves.x) + (amp23 * 0.5 * waves.y) + (amp23 * 0.3 * waves.z);\n let v = clamp(s + motion, 0.0, 1.0);\n return vec4f(v, 0.0, 0.0, 1.0);\n}\n",
|
|
782
|
+
"portal.wgsl": "// The portal clip \u2014 a slot's ground and frames are visible only through a\n// rounded rect on screen (CSS px): its FACE and, for a nested slot, every face\n// above it \u2014 the CHAIN (src/nav/portal.ts `Presentation`, `within`). A PURE\n// module every pass includes: the records arrive as parameters. `clips[i].y`\n// < 0.5 ends the chain (an empty chain is no clip at all, and the loop is one\n// compare); `clips[i].x` is a face's corner radius; `clips[i].z` its top\n// FEATHER (CSS px; 0 = a hard edge \u2014 design-018 \xA74, the tray's face: what it\n// shows fades in over that band below its top edge, with no code of its own).\n\n// = nav/portal.ts PORTAL_CHAIN, the uniform arrays' length; a mismatch fails to compile.\nconst PORTAL_CHAIN = 6u;\n\nfn portal_sd(p: vec2f, half: vec2f, r: f32) -> f32 {\n let rr = min(r, min(half.x, half.y));\n let q = abs(p) - half + vec2f(rr, rr);\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rr;\n}\n\n// Coverage at a CSS-px point through ONE face grown by `grow` device px: 1 inside, 0 outside, a one-device-px ramp across the edge \u2014\n// times, where the face has a feather, a smoothstep from 0 at its top edge to 1 the feather below it.\nfn portal_cover_one(p_css: vec2f, portal: vec4f, clip: vec4f, dpr: f32, grow: f32) -> f32 {\n let px = 1.0 / max(dpr, 1.0);\n let d = portal_sd(p_css - portal.xy, portal.zw, clip.x) - grow * px;\n let c = clamp(0.5 - d / px, 0.0, 1.0);\n if (clip.z <= 0.0) { return c; }\n let top = portal.y - portal.w;\n return c * smoothstep(top, top + clip.z, p_css.y);\n}\n\n// Coverage through the chain: the slot's own face grown by `grow` device px (the fill's one\n// px, so the hole's edge partitions with the plate \u2014 PORTAL.md \xA710) times every face above\n// it as it is. The product: a nested slot shows only where all of them agree.\nfn portal_cover_grown(p_css: vec2f, portals: array<vec4f, PORTAL_CHAIN>, clips: array<vec4f, PORTAL_CHAIN>, dpr: f32, grow: f32) -> f32 {\n var c = 1.0;\n for (var i = 0u; i < PORTAL_CHAIN; i++) {\n if (clips[i].y < 0.5) { break; }\n c *= portal_cover_one(p_css, portals[i], clips[i], dpr, select(0.0, grow, i == 0u));\n }\n return c;\n}\n\nfn portal_cover(p_css: vec2f, portals: array<vec4f, PORTAL_CHAIN>, clips: array<vec4f, PORTAL_CHAIN>, dpr: f32) -> f32 {\n return portal_cover_grown(p_css, portals, clips, dpr, 0.0);\n}\n",
|
|
783
|
+
"primitives.wgsl": "// 2D primitives for the desk's objects \u2014 the note, the whiteboard, the photo,\n// the mini mat. Pure functions, exact Euclidean distances \u2014 which is what\n// everything downstream (1-px AA, the analytic shadows, the hit-test mirrors,\n// src/sdf.ts) relies on. Prefixed `sdf_` so this module can share a program\n// with the mat's module without a name clash. Moved out of shaders/card/ when\n// the card frame retired (2026-09-25).\n// (Lineage: research/sdf-card/src/sdf/primitives.wgsl.)\n\nfn sdf_box(p: vec2f, b: vec2f) -> f32 {\n let q = abs(p) - b;\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0);\n}\n\nfn sdf_round_box(p: vec2f, b: vec2f, r: f32) -> f32 {\n let rr = clamp(r, 0.0, min(b.x, b.y));\n let q = abs(p) - b + rr;\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rr;\n}\n\nfn sdf_circle(p: vec2f, r: f32) -> f32 { return length(p) - r; }\n\nfn sdf_segment(p: vec2f, a: vec2f, b: vec2f) -> f32 {\n let pa = p - a;\n let ba = b - a;\n let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);\n return length(pa - ba * h);\n}\n\nfn sdf_rounded_x(p: vec2f, w: f32, r: f32) -> f32 {\n let q = abs(p);\n return length(q - min(q.x + q.y, w) * 0.5) - r;\n}\n\n// Exact circular fillet CSG (Quilez). Unlike smin/smax these put a TRUE circle\n// of radius r on the seam. Exact when the operands are perpendicular\n// half-planes (the retired card frame's corner was built to satisfy it).\n//\n// The union form clamps its operands to >= -r so the fillet stays local: the\n// unclamped hg_sdf form keeps growing once both surfaces are more than r\n// inside, and the field goes badly wrong in the interior.\nfn op_union_round(a: f32, b: f32, r: f32) -> f32 {\n let m = min(a, b);\n let A = max(a, -r);\n let B = max(b, -r);\n let u = max(vec2f(r - A, r - B), vec2f(0.0));\n return min(m, max(r, min(A, B)) - length(u));\n}\n\nfn op_isect_round(a: f32, b: f32, r: f32) -> f32 {\n let u = max(vec2f(r + a, r + b), vec2f(0.0));\n return min(-r, max(a, b)) + length(u);\n}\n",
|
|
784
|
+
"tray/tray-pass.wgsl": "// The pegboard tray's pass (design-017 \xA75; K5a; design-018 \xA72): drawn in the ROOT's render pass after the marks, premultiplied, two kinds\n// of quad \u2014 one vertex entry that lays each by its instance, two fragment entries (two pipelines on one layout: a branch over both in one\n// entry cost the board a third of its time, the register budget of the largest path paid by every pixel): instance 0 the drawer UNDER\n// its specimens, its edge included (`fs` \u2192 tray.wgsl `tray_drawer` \u2014 the view whole while the slide shows the dim, the drawer's box grown\n// by its shadows' reach while nothing dims; shut, nothing is drawn at all); from instance 1, one quad per specimen's ACCESSORY with its\n// shadow (`fs_accessory`, the records in `acc`). The rim's strips over it all retired with the rim (design-018 \xA72).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> t: TrayUniforms;\n@group(0) @binding(2) var noise_tex: texture_2d<f32>;\n@group(0) @binding(3) var noise_samp: sampler;\n@group(0) @binding(4) var hash_tex: texture_2d<f32>;\n@group(0) @binding(5) var<storage, read> acc: array<TrayAccessory>;\n\nstruct TrayOut {\n @builtin(position) pos: vec4f,\n @location(0) @interpolate(flat) mode: u32,\n @location(1) @interpolate(flat) item: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> TrayOut {\n var corners = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0), vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0));\n let k = corners[vi % 6u];\n let vw = t.view.x;\n let vh = t.view.y;\n var lo = vec2f(0.0, 0.0);\n var hi = vec2f(vw, vh);\n var o: TrayOut;\n o.mode = 0u;\n o.item = 0u;\n if (ii == 0u) {\n // dimming: the view whole; undimmed: the drawer's box from its shadows' reach above and beside it down to the view's bottom edge\n if (t.dim <= 0.0) {\n let reach = 3.0 * max(t.shadow.x, t.room.x) + length(t.shadow.zw);\n lo = vec2f(max(t.rect.x - reach, 0.0), max(t.rect.y - reach, 0.0));\n hi = vec2f(min(t.rect.x + t.rect.z + reach, vw), vh);\n }\n } else {\n o.mode = 1u;\n o.item = ii - 1u;\n let b = acc[o.item].box;\n lo = clamp(b.xy, vec2f(0.0), vec2f(vw, vh));\n hi = clamp(b.zw, vec2f(0.0), vec2f(vw, vh));\n }\n let css = mix(lo, hi, k);\n o.pos = vec4f(css.x / vw * 2.0 - 1.0, 1.0 - css.y / vh * 2.0, 0.0, 1.0);\n return o;\n}\n\n@fragment\nfn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n return tray_drawer(u, t, pos.xy, noise_tex, noise_samp, hash_tex);\n}\n\n@fragment\nfn fs_accessory(@builtin(position) pos: vec4f, @location(1) @interpolate(flat) item: u32) -> @location(0) vec4f {\n return tray_accessory(u, t, acc[item], pos.xy, noise_tex, noise_samp, hash_tex);\n}\n\n// THE VEIL (design-018 rev 5): one quad over the drawer's top \u2014 from its edge down through the header and its ramp \u2014 laid LAST, over\n// everything the drawer holds (tray.wgsl `tray_veil`: the plain board, whole in the header, fading out over the ramp).\n@vertex\nfn vs_veil(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n var corners = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0), vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0));\n let k = corners[vi % 6u];\n let vw = t.view.x;\n let vh = t.view.y;\n let lo = clamp(vec2f(t.rect.x, t.rect.y), vec2f(0.0), vec2f(vw, vh));\n let hi = clamp(vec2f(t.rect.x + t.rect.z, t.rect.y + t.shape.y + t.fade.y + t.fade.x + 1.0), vec2f(0.0), vec2f(vw, vh));\n let css = mix(lo, hi, k);\n return vec4f(css.x / vw * 2.0 - 1.0, 1.0 - css.y / vh * 2.0, 0.0, 1.0);\n}\n\n@fragment\nfn fs_veil(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n return tray_veil(u, t, pos.xy, noise_tex, noise_samp, hash_tex);\n}\n",
|
|
785
|
+
"tray/tray.wgsl": "// THE PEGBOARD TRAY (design-017 \xA75\u2013\xA76; design-018 \xA72\u2013\xA74) \u2014 the drawer, and the research board in it (research/sdf-pegboard, shader.js)\n// lying on the mat, shaded in CLOSED FORM for a head-on view: no march, no history, no jitter \u2014 every frame is final, so a scrolling\n// drawer is clean on every frame. Its edge is an arris in the face's material; its holes show the desk as drawn under it, black at the\n// board's shadow; under its top edge a clear HEADER (design-018 R4) where nothing hangs and no hole opens, then what hangs on it fades in\n// through the face's portal feather (every kind's `portal_cover`) and here, the accessories and the holes. A PURE module: the uniform blocks arrive as parameters \u2014 `u` the mat's struct carrying the DESK's light (the colour law\n// is mat.wgsl's own), `t` the tray's (src/tray/pass.ts) \u2014 and so does the blue noise.\n//\n// Board units are PITCHES (the research's 1). A board point is x from the drawer's left edge and Y down the board, Y split into a\n// whole row R (i32: the scroll's whole rows carried on the CPU plus the rows on screen) and fy \u2208 [0,1): nothing large ever enters\n// an f32, so the pattern is exact at any scroll (src/tray/lattice.ts is this arithmetic on the CPU, rounding half up as here).\n\n// ---- hashing: the research's pcg, on integer lattice points only (wrapping u32)\nfn peg_pcg(v: u32) -> u32 {\n let s = v * 747796405u + 2891336453u;\n let w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u;\n return (w >> 22u) ^ w;\n}\n\n// Four independent uniforms for a lattice cell (the research's hash2i4).\nfn peg_hash4(x: u32, y: u32, seed: u32) -> vec4f {\n let h0 = peg_pcg(x ^ peg_pcg(y + seed));\n let h1 = peg_pcg(h0);\n let h2 = peg_pcg(h1);\n let h3 = peg_pcg(h2);\n return vec4f(f32(h0), f32(h1), f32(h2), f32(h3)) * 2.3283064365386963e-10;\n}\n\n// ---- the board point\nstruct PegPoint { x: f32, R: i32, fy: f32 }\n\n// The same point moved by `d` pitches \u2014 the move's rows re-carried into R, so R stays exact.\nfn peg_move(p: PegPoint, d: vec2f) -> PegPoint {\n let y = p.fy + d.y;\n let fl = floor(y);\n return PegPoint(p.x + d.x, p.R + i32(fl), y - fl);\n}\n\n// ---- value noise on the board (the research's noise2, its quintic fade): the value and its gradient in lattice units. The corners'\n// hashes come PRE-GATHERED (src/tray/pass.ts `hashTexels`: texel (i, j) holds the research's pcg hash of lattice points (i, j),\n// (i+1, j), (i, j+1), (i+1, j+1), 8 bits each, tiled every 256 cells) \u2014 one load where the pcg took eight rounds. Each band reads\n// it at its own offset (its seed); the lattice point is an integer, so the noise stays exact at any row.\nfn peg_vnoise(ht: texture_2d<f32>, x: u32, y: u32, f: vec2f, seed: u32) -> vec3f {\n let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n let du = 30.0 * f * f * (f * (f - 2.0) + 1.0);\n let k = textureLoad(ht, vec2u((x + seed) & 255u, (y + ((seed * 2654435761u) >> 24u)) & 255u), 0);\n let a = k.r;\n let b = k.g;\n let c = k.b;\n let d = k.a;\n let v = mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n return vec3f(v, du.x * mix(b - a, d - c, u.y), du.y * mix(c - a, d - b, u.x));\n}\n\n// One band at (fx, fy) cells per pitch (integers): its lattice row is R\xB7fy plus the cells within the row, in wrapping u32 \u2014 seamless\n// and exact at any R. The value, and its gradient in pitches.\nfn peg_band(ht: texture_2d<f32>, p: PegPoint, fx: u32, fy: u32, seed: u32) -> vec3f {\n let X = p.x * f32(fx);\n let ix = floor(X);\n let Y = p.fy * f32(fy);\n let iy = floor(Y);\n let n = peg_vnoise(ht, u32(i32(ix)), u32(p.R) * fy + u32(i32(iy)), vec2f(X - ix, Y - iy), seed);\n return vec3f(n.x, n.y * f32(fx), n.z * f32(fy));\n}\n\n// \u230Aa / b\u230B for b > 0 (WGSL's `/` truncates toward zero).\nfn peg_floor_div(a: i32, b: i32) -> i32 {\n let q = a / b;\n return select(q, q - 1, (a % b != 0) && (a < 0));\n}\n\n// Value noise on a TURNED lattice, exact at any row: lattice coordinates M\xB7(x, Y)\xB7num/den, M = [[a, \u2212b], [b, a]] an INTEGER rotation\n// and scale (cells turned atan(b/a), num\xB7\u221A(a\xB2 + b\xB2)/den of them to a pitch), so no octave's cells line up with the board or with\n// another octave's \u2014 value noise's square cells stop reading as squares. The carried row splits R = den\xB7Q + r: Q's share is an\n// exact integer (\u2212b\xB7num\xB7Q, a\xB7num\xB7Q); only x, r and fy, all small, are ever a float. One pre-gathered load.\nfn peg_rot(ht: texture_2d<f32>, p: PegPoint, a: i32, b: i32, num: i32, den: i32, seed: u32) -> f32 {\n let q = peg_floor_div(p.R, den);\n let r = f32(p.R - q * den) + p.fy;\n let uv = vec2f(f32(a) * p.x - f32(b) * r, f32(b) * p.x + f32(a) * r) * (f32(num) / f32(den));\n let cell = floor(uv);\n let base = vec2i(i32(cell.x) - b * num * q, i32(cell.y) + a * num * q);\n return peg_vnoise(ht, u32(base.x), u32(base.y), uv - cell, seed).x;\n}\n\n// The research's \"fine detail fading with pixel footprint\", per band: F cycles per pitch are kept while a cycle spans \u2265 4 device\n// px and gone at 2, so a scrolling board never shimmers (a fleck by its thin side, not its cell). The footprint is the frame's, so\n// every band's share is a UNIFORM (pass.ts `keep` \u2014 `1 \u2212 smoothstep(\xBC, \xBD, fp\xB7F)`): the face's grain (\u2248 19) \xB7 85 \xB7 190, then\n// 210 \xB7 42 \xB7 105, the punched fibre's 9 \xB7 18 \xB7 36.\n\n// Sparse fibre flecks (the research's `flecks`): one randomly turned ellipse per jittered cell at F per pitch, signed \u2014 dark\n// negative, pale positive. A stroke's thin side is sub-pixel long before its cell is (\u2248 0.3 of it): its edge is widened by the pixel's\n// footprint in the ellipse's own measure and its peak lowered to keep its ink \u2014 analytic AA, the stroke a faint line, never a dash\n// (the research averaged 1024 jittered samples to the same end). `fp` = pitches per device px.\nfn peg_flecks(p: PegPoint, F: u32, density: f32, seed: u32, fp: f32) -> f32 {\n let X = p.x * f32(F);\n let ix = floor(X);\n let Y = p.fy * f32(F);\n let iy = floor(Y);\n let cx = u32(i32(ix));\n let cy = u32(p.R) * F + u32(i32(iy));\n let q = vec2f(X - ix, Y - iy);\n var acc = 0.0;\n for (var j = -1; j <= 1; j++) {\n for (var i = -1; i <= 1; i++) {\n let r = peg_hash4(cx + u32(i), cy + u32(j), seed);\n if (r.w > density) { continue; }\n let d = q - (vec2f(f32(i), f32(j)) + r.xy);\n let ang = r.z * 3.14159265;\n let ca = cos(ang);\n let sa = sin(ang);\n let dl = vec2f(d.x * ca + d.y * sa, -d.x * sa + d.y * ca);\n let wid = 0.10 + 0.10 * r.y;\n let e = length(vec2f(dl.x / (0.35 + 0.65 * r.x), dl.y / wid));\n let aa = fp * f32(F) / wid;\n acc += (1.0 - smoothstep(0.6 - aa, 1.0 + aa, e)) / (1.0 + 2.0 * aa) * select(-1.0, 0.8, r.z > 0.62);\n }\n }\n return clamp(acc, -1.0, 1.0);\n}\n\n// ---- the lattice (the research's cellCoord \xB7 sdStadiumV)\nstruct PegHole { q: vec2f, d: f32, g: vec2f }\n\n// The vertical stadium at an offset from its centre: the distance (negative inside) and its gradient, pointing out of the hole.\nfn peg_stadium(t: TrayUniforms, q: vec2f) -> vec3f {\n let e = vec2f(q.x, q.y - clamp(q.y, -t.hole.y, t.hole.y));\n let len = max(length(e), 1.0e-6);\n return vec3f(len - t.hole.x, e / len);\n}\n\n// The hole of the nearest row (a hole's half-height, 0.33, is under half a pitch): rows at Y = r + \xBE, odd rows shifted \xBD \u2014 the\n// stagger's parity from the exact row index. A hole whose centre lies within the solid border of a side is never punched.\nfn peg_hole(t: TrayUniforms, p: PegPoint) -> PegHole {\n let dr = floor(p.fy - t.depth.w + 0.5);\n let row = p.R + i32(dr);\n let shift = t.depth.z + select(0.0, 0.5, (row & 1) != 0);\n let hx = p.x - shift;\n let col = floor(hx + 0.5);\n var h: PegHole;\n h.q = vec2f(hx - col, p.fy - dr - t.depth.w);\n let s = peg_stadium(t, h.q);\n h.d = s.x;\n h.g = s.yz;\n let cx = col + shift;\n if ((cx < t.hole.w) || (cx > t.rect.z / t.view.w - t.hole.w)) { h.d = 1.0e3; }\n return h;\n}\n\n// ---- the materials (the research's boardMat \xB7 wallMat), linear albedo\n// The tempered face: the TONE drift \u2014 two smooth octaves on lattices turned against the board and each other (cells 2.24 and 0.89\n// of a pitch), the research's big and mid fbm as a UI pitch shows them \u2014 and the fine fibre GRAIN on a third (cells 0.05 of a pitch,\n// \u2248 19 a pitch: what survives of the research's fine grain where its footprint lets it), then at a reading pitch the research's fine\n// height grain and its bump, the fibre flecks \u2014 every band faded by its own footprint. Out: the grain as one factor on the face's\n// linear albedo (`g`: the albedo is face\xB7(1 + g)), the height's gradient (the bump), and whether the face is FLAT (every bump faded).\nstruct PegFace { g: f32, grad: vec2f, flat: bool }\nfn peg_face(ht: texture_2d<f32>, t: TrayUniforms, p: PegPoint, fp: f32) -> PegFace {\n let tone = 0.62 * (peg_rot(ht, p, 2, 1, 1, 5, 0x3a1u) - 0.5) + 0.38 * (peg_rot(ht, p, 1, -2, 1, 2, 0x5c7u) - 0.5);\n var grain = 0.0;\n let kg = t.keepFace.x;\n if (kg > 0.0) { grain = kg * (peg_rot(ht, p, 3, 1, 6, 1, 0x2b1u) - 0.5); }\n // the fine height (the research's faceHeight: 0.55 \xB7 85, 0.30 \xB7 190\xD770, 0.15 \xB7 60\xD7210) \u2014 at a UI pitch its footprint fades it\n var h = 0.0;\n var grad = vec2f(0.0);\n let k85 = t.keepFace.z;\n let k190 = t.keepFace.w;\n let k210 = t.keepFine.x;\n if (k85 > 0.0) { let n = peg_band(ht, p, 85u, 85u, 0x1f3u); h += 0.55 * k85 * (n.x - 0.5); grad += 0.55 * k85 * n.yz; }\n if (k190 > 0.0) { let n = peg_band(ht, p, 190u, 70u, 0x4a7u); h += 0.30 * k190 * (n.x - 0.5); grad += 0.30 * k190 * n.yz; }\n if (k210 > 0.0) { let n = peg_band(ht, p, 60u, 210u, 0x7c9u); h += 0.15 * k210 * (n.x - 0.5); grad += 0.15 * k210 * n.yz; }\n let k42 = t.keepFine.y;\n let k105 = t.keepFine.z;\n var fl = 0.0;\n if (k42 > 0.0) { fl += 0.30 * k42 * peg_flecks(p, 42u, 0.40, 0xa13u, fp); }\n if (k105 > 0.0) { fl += 0.18 * k105 * peg_flecks(p, 105u, 0.35, 0xc35u, fp); }\n var f: PegFace;\n f.g = (1.0 + 0.12 * tone + 0.08 * grain + 0.40 * h) * (1.0 + fl) - 1.0;\n f.grad = grad;\n f.flat = (k85 <= 0.0) && (k190 <= 0.0) && (k210 <= 0.0);\n return f;\n}\n\n// The paler, fuzzier punched fibre (the research's edge): its clumps at 9 \xB7 18 \xB7 36 per pitch, faded by the footprint.\nfn peg_edge(ht: texture_2d<f32>, t: TrayUniforms, p: PegPoint, fp: f32) -> vec3f {\n var clump = 0.0;\n let k9 = t.keepEdge.x;\n let k18 = t.keepEdge.y;\n let k36 = t.keepEdge.z;\n if (k9 > 0.0) { clump += 0.5 * k9 * (peg_band(ht, p, 9u, 9u, 0x3e5u).x - 0.5); }\n if (k18 > 0.0) { clump += 0.25 * k18 * (peg_band(ht, p, 18u, 18u, 0x5a9u).x - 0.5); }\n if (k36 > 0.0) { clump += 0.125 * k36 * (peg_band(ht, p, 36u, 36u, 0x7bdu).x - 0.5); }\n return t.edge.xyz * (1.0 + 0.30 * clump / 0.875);\n}\n\n// ---- the light: the research's HOME lamp (D-K3.4), in the desk's colour law and room\n// The desk's colour law on the tray (the paper's `paper_colour`, chain 0): by day the mat's grade on the sRGB albedo \u2014 lit (`gobo`\n// 1) the identity, a configured byte is the drawn byte; in shadow the room's \xBD and its touch of saturation \u2014 by night the Moon and\n// the eye (mat.wgsl `night_mat`) on the linear albedo; between, the cross-fade.\nfn tray_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32) -> vec3f {\n let day = shade_mat(u, albedo, gobo);\n if (u.night.x <= 0.0) { return day; }\n let night = night_mat(u, srgb_to_linear(albedo), gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(day, night, u.night.x);\n}\n\n// A surface under the lamp, its linear albedo and normal \u2192 the colour on screen. The relief is the paper's \u2014 `(N\xB7L/L_z)^(1/2.2)`, the\n// flat face exactly 1 \u2014 and only brightens; a normal turned from the lamp takes the room's grade instead (`gobo` = the Lambert term),\n// so a face turned away reads as a shadow does, never black. `vis`: the lamp seen at all. The glint is the photo's.\nfn tray_lit(u: MatUniforms, t: TrayUniforms, alb: vec3f, n: vec3f, vis: f32, noise: f32) -> vec3f {\n let L = t.lamp.xyz;\n let lam = clamp(dot(n, L) / max(L.z, 1.0e-3), 0.0, 1.6);\n let srgb = clamp(night_encode(alb) * pow(max(lam, 1.0), 1.0 / 2.2), vec3f(0.0), vec3f(1.0));\n var c = tray_colour(u, srgb, min(lam, 1.0) * vis, noise);\n let H = normalize(L + vec3f(0.0, 0.0, 1.0));\n let spec = pow(saturate(dot(n, H)), 48.0) * 0.08 * vis;\n let glint = mix(vec3f(1.0, 0.985, 0.95), vec3f(0.78, 0.86, 1.0) * 0.5, saturate(u.night.x));\n return c + glint * spec;\n}\n\n// ---- the board: the face and the rim fillet, and the desk through a hole\n// The front surface at a board point: the face, or \u2014 within k of a hole \u2014 the fillet. On smax(slab, \u2212hole, k) = 0 seen head-on the\n// surface's blend weight is h = \u221A(d/k) for d \u2208 [0,k], and its normal normalize(((h \u2212 1)\xB7\u2207d, h)) sweeps from the hole's wall to the\n// face: the rim's lit edge and its shadowed one, in the paler punched fibre where the normal is steep (the research's faceness).\nfn peg_surface(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: PegPoint, h: PegHole, fp: f32, noise: f32) -> vec3f {\n let k = t.hole.z;\n let face = peg_face(ht, t, p, fp);\n // the PLAIN FACE \u2014 clear of every fillet, its bump faded: N\xB7L is L_z exactly, the relief 1, the glint nothing; the grain's small\n // linear factor rides the configured byte to first order ((1 + g)^(1/2.4) \u2248 1 + g/2.4 \u2014 the byte itself where the grain is 0)\n if ((h.d >= k) && face.flat) {\n return tray_colour(u, clamp(t.faceSrgb.xyz * (1.0 + face.g / 2.4), vec3f(0.0), vec3f(1.0)), 1.0, noise);\n }\n var n = normalize(vec3f(-face.grad * 0.003, 1.0)); // the research's bump: 0.0030 per unit of the height's slope\n if (h.d < k) {\n let s = sqrt(max(h.d, 0.0) / k);\n n = normalize(vec3f((s - 1.0) * h.g, s));\n }\n let faceness = smoothstep(0.35, 0.75, n.z);\n var alb = t.face.xyz * (1.0 + face.g);\n if (faceness < 1.0) { alb = mix(peg_edge(ht, t, p, fp) * 0.86, alb, faceness); } // the punched rim, burnished at the front\n return tray_lit(u, t, alb, n, 1.0, noise);\n}\n\n// Through a hole: THE DESK AS DRAWN (design-018 \xA73). A hole is cut through the board, so it shows what lies under the drawer this\n// frame \u2014 the mat's green, its grid lines, the leaf shadows, anything laid there \u2014 in the board's shadow: premultiplied BLACK over the\n// frame, at the alpha that leaves `f` of the light. THE TWO PLANES (design-017 \xA76.4) give the lamp at the mat: the ray from the mat\n// toward the lamp crosses the board's back face at A and its front face at B; the mat is lit iff both lie in the SAME hole \u2014 B's, with\n// A tested against it (a convex prism holds the segment iff it holds both ends) \u2014 the penumbra from the SDF distance, widened by the\n// lamp's size over each plane's height. The room's light reaches the mat through the hole, cut by the cavity. f = a\xB7cav + (1 \u2212 a)\xB7vis,\n// `a` the room's share (the mat's own shade-in-shadow: shade_mat at gobo 0) \u2014 ENCODED as the mat's own chain encodes its shade (its\n// grade lives in pow-2.2 space), so the densest hole is the mat's shade under a leaf \u2014 over the desk dimmed as the rest of it is.\nfn peg_through(u: MatUniforms, t: TrayUniforms, p: PegPoint, h: PegHole, fp: f32) -> f32 {\n let L = t.lamp.xyz;\n let lz = max(L.z, 1.0e-3);\n let off = L.xy / lz;\n let thick = t.depth.x;\n let gap = t.depth.y;\n let hb = peg_hole(t, peg_move(p, off * (gap + thick)));\n let da = peg_stadium(t, hb.q - off * thick).x;\n // the penumbra: the lamp's angular radius over the ray's length to each plane's edge (height / L_z), and a pixel\n let wa = t.lamp.w * gap / lz + fp;\n let wb = t.lamp.w * (gap + thick) / lz + fp;\n let vis = smoothstep(-wb, wb, -hb.d) * smoothstep(-wa, wa, -da);\n let cav = mix(t.cavity.x, t.cavity.y, smoothstep(0.0, t.cavity.z, -h.d));\n let a = 1.0 - u.gobo.z * (1.0 - u.gobo.w);\n let f = a * cav + (1.0 - a) * vis;\n return 1.0 - (1.0 - t.dim) * pow(f, 1.0 / 2.2);\n}\n\n// ---- the drawer\nfn tray_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let k = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * k - 1.453152027) * k) + 1.421413741) * k - 0.284496736) * k + 0.254829592) * k * exp(-a * a);\n return s * y;\n}\n\n// Gaussian-blurred coverage of an outline at distance d (the shadow, straight out of the field \u2014 the paper's).\nfn tray_blur(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * tray_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n\n// The drawer's outline (CSS px, negative inside): a box whose top corners round and whose bottom lies past the view \u2014 what is drawn\n// and what casts the shadows (the finger notch retired \u2014 design-018 \xA72).\nfn tray_outline(t: TrayUniforms, p: vec2f) -> f32 {\n let r = t.shape.x;\n let ext = vec2f(0.5 * t.rect.z, 0.5 * t.rect.w + r);\n return sdf_round_box(p - vec2f(t.rect.x + ext.x, t.rect.y + ext.y), ext, r);\n}\n\n// The board point under a pixel (CSS px) \u2014 the rows on screen added to the carried rows in i32.\nfn tray_point(t: TrayUniforms, p: vec2f) -> PegPoint {\n let ly = (p.y - t.rect.y) / t.view.w + t.frac;\n let fl = floor(ly);\n return PegPoint((p.x - t.rect.x) / t.view.w, t.rowBase + i32(fl), ly - fl);\n}\n\n// The board's EDGE (design-018 \xA72): an ARRIS \u2014 a quarter-round `A` px wide where the face meets the outline, in the FACE's own\n// material (its tone at the board point under it, so the grain runs on without a seam; not the paler punched fibre). Its normal turns\n// from the eye (on the face's side) to the outline's outward direction (at the outline): it catches the lamp where the outline faces\n// it \u2014 the top and the left \u2014 and falls to the room's shade on the far side. `o`: the outline's distance there (\u2212A \u2026 0).\nfn tray_arris(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: vec2f, o: f32, noise: f32) -> vec3f {\n let e = 0.5;\n let gx = tray_outline(t, p + vec2f(e, 0.0)) - tray_outline(t, p - vec2f(e, 0.0));\n let gy = tray_outline(t, p + vec2f(0.0, e)) - tray_outline(t, p - vec2f(0.0, e));\n let g = vec2f(gx, gy) / max(length(vec2f(gx, gy)), 1.0e-6);\n let s = clamp(1.0 + o / t.shape.y, 0.0, 1.0);\n let n = vec3f(s * g, sqrt(max(1.0 - s * s, 0.0)));\n let face = peg_face(ht, t, tray_point(t, p), t.fp);\n return tray_lit(u, t, t.face.xyz * (1.0 + face.g), n, 1.0, noise);\n}\n\n// The board to its edge, PREMULTIPLIED: the point under the carry, its hole \u2014 the front surface over it by its analytic coverage, and\n// where the surface is not, the desk seen through (black at the hole's alpha). `holes` scales every hole's OPENING: 1 as punched (the\n// board under the specimens), 0 CLOSED \u2014 the plain face, its fillets' relief flattened into it (the VEIL's board, `tray_veil`).\nfn tray_board(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: vec2f, noise: f32, holes: f32) -> vec4f {\n let pt = tray_point(t, p);\n let h = peg_hole(t, pt);\n let cg = clamp(0.5 + h.d / t.fp, 0.0, 1.0); // the surface's analytic coverage as punched\n let open = (1.0 - cg) * holes; // what of the pixel the hole opens\n var col = vec4f(0.0);\n if (holes < 1.0) {\n // closing: the plain face where the hole no longer opens, and the fillet (within `hole.z` of it) turning to the face\n var plain = h;\n plain.d = 1.0e3;\n let face = peg_surface(ht, u, t, pt, plain, t.fp, noise);\n var s = face;\n if (cg > 0.0 && h.d < t.hole.z) { s = mix(face, peg_surface(ht, u, t, pt, h, t.fp, noise), holes); }\n col = vec4f(s, 1.0) * cg + vec4f(face, 1.0) * ((1.0 - cg) * (1.0 - holes));\n } else if (cg > 0.0) {\n col = vec4f(peg_surface(ht, u, t, pt, h, t.fp, noise), 1.0) * cg;\n }\n if (open > 0.0) { col += vec4f(0.0, 0.0, 0.0, peg_through(u, t, pt, h, t.fp) * open); }\n return col;\n}\n\n// A pixel INSIDE the drawer's outline (`o` its distance there, `px` a device px in CSS px), premultiplied: the edge within `A` of the\n// outline, the board inside it with its holes at `holes` \u2014 blended across the pixel where they meet \u2014 and the edge's INNER SHADOW over\n// the board (D-R1.2): the window's lip on the lamp's side, black at up to `shape.z`, fading over `shape.w` px inside the edge, where the\n// outline faces the lamp (the push of the drawer's lamp shadow points away from it).\nfn tray_inside(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: vec2f, px: f32, o: f32, noise: f32, holes: f32) -> vec4f {\n let k = clamp(0.5 + (o + t.shape.y) / px, 0.0, 1.0);\n var col = vec4f(0.0);\n if (k > 0.0) { col = vec4f(tray_arris(ht, u, t, p, o, noise), 1.0); }\n if (k < 1.0) {\n var b = tray_board(ht, u, t, p, noise, holes);\n if (t.shape.z > 0.0 && -o - t.shape.y < t.shape.w) {\n let e = 0.5;\n let g = normalize(vec2f(tray_outline(t, p + vec2f(e, 0.0)) - tray_outline(t, p - vec2f(e, 0.0)), tray_outline(t, p + vec2f(0.0, e)) - tray_outline(t, p - vec2f(0.0, e))) + vec2f(1.0e-6, 0.0));\n let facing = max(dot(g, -normalize(t.shadow.zw)), 0.0);\n let sh = t.shape.z * facing * (1.0 - smoothstep(0.0, t.shape.w, -o - t.shape.y));\n b = vec4f(b.rgb * (1.0 - sh), 1.0 - (1.0 - b.a) * (1.0 - sh));\n }\n col = mix(b, col, k);\n }\n return col;\n}\n\n// One pixel of the tray UNDER its specimens (`frag` in device px), the view whole: the dim over the desk, the drawer's shadows on it,\n// the board to the outline and its edge, every hole as punched \u2014 premultiplied, each pixel drawn once. The board scrolls under its edge\n// (the drawer is a window onto it). The specimens hang over the board inside the edge and under the header (their face clip,\n// tray/specimens.ts `faceClip`); the VEIL (`tray_veil`) is laid over them all at the top.\nfn tray_drawer(u: MatUniforms, t: TrayUniforms, frag: vec2f, noise_tex: texture_2d<f32>, noise_samp: sampler, ht: texture_2d<f32>) -> vec4f {\n let dpr = t.view.z;\n let p = frag / dpr;\n let px = 1.0 / dpr;\n let dim = vec4f(0.0, 0.0, 0.0, t.dim);\n // beyond the shadows' reach: the dim alone\n let reach = 3.0 * max(t.shadow.x, t.room.x) + length(t.shadow.zw);\n if ((p.x < t.rect.x - reach) || (p.x > t.rect.x + t.rect.z + reach) || (p.y < t.rect.y - reach)) { return dim; }\n // the blue noise is the NIGHT's (the rods' snow, mat.wgsl `night_mat`): by day the board takes none \u2014 its own grain never bands,\n // and a scrolled board is then the same pixels, moved\n var bn = vec3f(0.5);\n if (u.night.x > 0.0) { bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb; }\n // DEEP INSIDE \u2014 clear of the rounded corners, the edge, its inner shadow and two px more: no edge, no shadow, no outline to evaluate\n // (premultiplied \u2014 a hole's alpha is its shadow over the desk, no longer 1)\n let m = t.shape.y + t.shape.w + 2.0;\n if ((p.x > t.rect.x + m) && (p.x < t.rect.x + t.rect.z - m) && (p.y > t.rect.y + t.shape.x + m)) {\n return tray_board(ht, u, t, p, bn.y, 1.0);\n }\n let o = tray_outline(t, p);\n let cover = clamp(0.5 - o / px, 0.0, 1.0);\n // the shadows on the desk \u2014 the room's round the outline, the lamp's pushed along its ground direction, one over the other \u2014 where\n // the drawer does not cover them\n var under = dim;\n if (cover < 1.0) {\n let room = t.room.y * tray_blur(o, t.room.x);\n let lamp = t.shadow.y * tray_blur(tray_outline(t, p - t.shadow.zw), t.shadow.x);\n under = vec4f(0.0, 0.0, 0.0, 1.0 - (1.0 - room) * (1.0 - lamp) * (1.0 - t.dim));\n }\n if (cover <= 0.0) { return under; }\n return tray_inside(ht, u, t, p, px, o, bn.y, 1.0) * cover + under * (1.0 - cover);\n}\n\n// THE VEIL (design-018 rev 5 \u2014 James, 2026-09-28: \"at the edge fading, somehow the holes are displayed on top of objects\"): the PLAIN\n// board \u2014 its face with every hole closed, its edge, the edge's inner shadow: `tray_drawer`'s own pixel, less its holes \u2014 laid OVER\n// everything the drawer holds (the accessories, the specimens, their tags), whole in the HEADER (`fade.y` under the edge's inside) and\n// fading out over the ramp below it (`fade.x`): `1 \u2212 smoothstep(foot, foot + fade.x, y)`. What hangs there and the holes behind it fade\n// INTO THE BOARD together \u2014 a specimen never turns see-through, so no hole shows through it (the specimens are opaque to the face\n// clip's top; a hard clip at the header's foot lies under the veil's whole). Only pixels wholly inside the outline: its antialiased rim\n// is `tray_drawer`'s, and the veil there would lay the edge twice.\nfn tray_veil(u: MatUniforms, t: TrayUniforms, frag: vec2f, noise_tex: texture_2d<f32>, noise_samp: sampler, ht: texture_2d<f32>) -> vec4f {\n let p = frag / t.view.z;\n let px = 1.0 / t.view.z;\n let foot = t.rect.y + t.shape.y + t.fade.y;\n let a = 1.0 - smoothstep(foot, foot + t.fade.x, p.y);\n if (a <= 0.0) { return vec4f(0.0); }\n let o = tray_outline(t, p);\n if (o > -0.5 * px) { return vec4f(0.0); }\n var bn = vec3f(0.5);\n if (u.night.x > 0.0) { bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb; }\n return tray_inside(ht, u, t, p, px, o, bn.y, 0.0) * a;\n}\n\n// ---- the accessories (K5a): what the specimens hang on, plugged into real holes \u2014 SK\xC5DIS's hook, shelf, clip and rail seen head-on, in\n// their powder coat under the lamp, each casting its shadow on the board. One instance per specimen (tray-pass.wgsl), its quad the\n// accessory and its shadow's reach; CSS px, the pitch `P` the unit of every length (kernel/tray.ts TRAY_ACCESSORY's numbers).\nfn acc_peg(a: TrayAccessory, i: u32) -> vec2f {\n if (i == 0u) { return a.pegs0.xy; }\n if (i == 1u) { return a.pegs0.zw; }\n if (i == 2u) { return a.pegs1.xy; }\n return a.pegs1.zw;\n}\n\n// The accessory's silhouette: its signed distance at `p` (negative inside) \u2014 every peg's PLUG in its hole, and the accessory's body.\nfn acc_sdf(a: TrayAccessory, p: vec2f, P: f32) -> f32 {\n let kind = u32(a.kind.x + 0.5);\n let n = min(u32(a.kind.y + 0.5), 4u);\n let r = a.rect;\n var d = 1.0e4;\n var lo = 1.0e9;\n var hi = -1.0e9;\n for (var i = 0u; i < n; i++) {\n let g = acc_peg(a, i);\n lo = min(lo, g.x);\n hi = max(hi, g.x);\n d = min(d, sdf_round_box(p - g, vec2f(0.1, 0.28) * P, 0.09 * P)); // the plug, in its hole\n if (kind == 0u) { d = min(d, sdf_segment(p, g, g + vec2f(0.0, 0.58 * P)) - 0.07 * P); } // a hook: down to its tip\n if (kind == 1u) { d = min(d, sdf_segment(p, g, vec2f(g.x, r.w + 0.1 * P)) - 0.06 * P); } // a shelf's bracket, up to the plank\n if (kind == 2u) { // a clip: its spring from the hole down over the specimen's top edge\n let top = g.y - 0.22 * P;\n let foot = max(g.y, r.y) + 0.5 * P;\n d = min(d, sdf_round_box(p - vec2f(g.x, 0.5 * (top + foot)), vec2f(0.24 * P, 0.5 * (foot - top)), 0.08 * P));\n }\n }\n if (kind == 1u) { // the shelf's plank, under the specimen and past its sides\n let hw = 0.5 * (r.z - r.x) + 0.3 * P;\n d = min(d, sdf_round_box(p - vec2f(0.5 * (r.x + r.z), r.w + 0.12 * P), vec2f(hw, 0.12 * P), 0.05 * P));\n }\n if (kind == 3u && n > 0u) { // the rail: a bar through its holes, past the outer two\n let y = acc_peg(a, 0u).y;\n d = min(d, sdf_round_box(p - vec2f(0.5 * (lo + hi), y), vec2f(0.5 * (hi - lo) + 0.4 * P, 0.11 * P), 0.11 * P));\n }\n return d;\n}\n\n// One pixel of an accessory's quad: its shadow on the board (the silhouette seen from the lamp, pushed off by how far it stands off the\n// board, softened by the lamp's size over that), then the accessory over it \u2014 its rounded relief from the silhouette's gradient, lit as\n// the board's edge is; both kept inside that edge. Premultiplied.\nfn tray_accessory(u: MatUniforms, t: TrayUniforms, a: TrayAccessory, frag: vec2f, noise_tex: texture_2d<f32>, noise_samp: sampler, ht: texture_2d<f32>) -> vec4f {\n let p = frag / t.view.z;\n let px = 1.0 / t.view.z;\n let P = t.view.w;\n // inside the board's edge \u2014 whole: in the header and its ramp the VEIL lays the plain board over it, as over the specimens (design-018\n // rev 5, `tray_veil`)\n let inside = clamp(0.5 - (tray_outline(t, p) + t.shape.y) / px, 0.0, 1.0);\n if (inside <= 0.0) { return vec4f(0.0); }\n let L = t.lamp.xyz;\n let lz = max(L.z, 1.0e-3);\n let off = L.xy / lz * a.kind.z;\n let sig = t.lamp.w * a.kind.z / lz + px;\n let ds = acc_sdf(a, p + off, P);\n let shadow = t.accessory.w * (1.0 - smoothstep(-sig, sig, ds));\n let d = acc_sdf(a, p, P);\n let c = clamp(0.5 - d / px, 0.0, 1.0);\n if ((c <= 0.0) && (shadow <= 1.0e-4)) { return vec4f(0.0); }\n var col = vec3f(0.0);\n if (c > 0.0) {\n let e = 0.5;\n let gx = acc_sdf(a, p + vec2f(e, 0.0), P) - acc_sdf(a, p - vec2f(e, 0.0), P);\n let gy = acc_sdf(a, p + vec2f(0.0, e), P) - acc_sdf(a, p - vec2f(0.0, e), P);\n let g = vec2f(gx, gy) / max(length(vec2f(gx, gy)), 1.0e-6);\n let s = clamp(-d / (0.07 * P), 0.0, 1.0);\n let n = normalize(vec3f(g * (1.0 - s) * 1.2, max(s, 0.2)));\n var bn = vec3f(0.5);\n if (u.night.x > 0.0) { bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb; }\n col = tray_lit(u, t, t.accessory.xyz, n, 1.0, bn.y);\n }\n let body = c * inside;\n let under = shadow * inside;\n return vec4f(col * body, body + under * (1.0 - body));\n}\n"
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// ../desk/src/shaders.ts
|
|
789
|
+
function shaderText(files) {
|
|
790
|
+
const out = {};
|
|
791
|
+
for (const [key, file] of Object.entries(files)) {
|
|
792
|
+
const text = WGSL[file];
|
|
793
|
+
if (text === void 0) throw new Error(`shaders: no generated text for "${file}" \u2014 run gen:shaders`);
|
|
794
|
+
out[key] = text;
|
|
795
|
+
}
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// ../desk/src/kit/wgsl.ts
|
|
800
|
+
var PIECES = {
|
|
801
|
+
view: { structs: [MatUniforms] },
|
|
802
|
+
portal: { file: "portal.wgsl" },
|
|
803
|
+
sdf: { file: "primitives.wgsl" },
|
|
804
|
+
light: { file: "mat/mat.wgsl" },
|
|
805
|
+
ruler: { file: "mat/ruler.wgsl" },
|
|
806
|
+
book: { structs: [NbUniforms, NbBook], file: "kit/book.wgsl" }
|
|
807
|
+
};
|
|
808
|
+
var ALIASES = { lamp: "light", gobo: "light", night: "light", noise: "light" };
|
|
809
|
+
var KIT_WGSL_FILES = Object.fromEntries(
|
|
810
|
+
Object.entries(PIECES).flatMap(([name, p]) => "file" in p ? [[name, p.file]] : [])
|
|
811
|
+
);
|
|
812
|
+
function kitWgsl(names, own, text = shaderText) {
|
|
813
|
+
const want = /* @__PURE__ */ new Set();
|
|
814
|
+
for (const n of names) {
|
|
815
|
+
const piece = ALIASES[n] ?? n;
|
|
816
|
+
if (!(piece in PIECES)) throw new Error(`kitWgsl: no piece "${n}" (the kit's: ${[...Object.keys(PIECES), ...Object.keys(ALIASES)].join(", ")})`);
|
|
817
|
+
want.add(piece);
|
|
818
|
+
}
|
|
819
|
+
const files = {};
|
|
820
|
+
for (const [name, p] of Object.entries(PIECES)) if (want.has(name) && p.file) files[name] = p.file;
|
|
821
|
+
const read = text(files);
|
|
822
|
+
const structs = [];
|
|
823
|
+
const modules = [];
|
|
824
|
+
for (const [name, p] of Object.entries(PIECES)) {
|
|
825
|
+
if (!want.has(name)) continue;
|
|
826
|
+
if (p.structs) structs.push(...p.structs);
|
|
827
|
+
if (p.file) modules.push({ label: p.file, text: read[name] });
|
|
828
|
+
}
|
|
829
|
+
return { structs: [...structs, ...own.structs ?? []], modules: [...modules, ...own.modules ?? []], entry: own.entry };
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// ../desk/src/kit/layer.ts
|
|
833
|
+
var LAYER_COMPOSITE_FILE = "kit/composite.wgsl";
|
|
834
|
+
function layerComposite(text) {
|
|
835
|
+
return kitWgsl(["view", "portal"], { entry: { label: LAYER_COMPOSITE_FILE, text: text({ composite: LAYER_COMPOSITE_FILE }).composite } }, text);
|
|
836
|
+
}
|
|
837
|
+
function layerCompositeLayout(device, label) {
|
|
838
|
+
return bindLayout(device, [{ binding: 0, stages: ["fragment"], texture: "float" }, { binding: 1, stages: ["fragment"], buffer: "uniform" }, { binding: 2, stages: ["fragment"], buffer: "uniform" }], label);
|
|
839
|
+
}
|
|
840
|
+
var attachmentOf = (v) => ({
|
|
841
|
+
w: Math.max(1, Math.round(v.width * v.dpr)),
|
|
842
|
+
h: Math.max(1, Math.round(v.height * v.dpr))
|
|
843
|
+
});
|
|
844
|
+
function clip(a, b) {
|
|
845
|
+
const x0 = Math.max(a[0], b[0]);
|
|
846
|
+
const y0 = Math.max(a[1], b[1]);
|
|
847
|
+
const x1 = Math.min(a[0] + a[2], b[0] + b[2]);
|
|
848
|
+
const y1 = Math.min(a[1] + a[3], b[1] + b[3]);
|
|
849
|
+
return [x0, y0, Math.max(0, x1 - x0), Math.max(0, y1 - y0)];
|
|
850
|
+
}
|
|
851
|
+
var LayeredKind = class {
|
|
852
|
+
/** The root slot's pass — the host's door to its own API (the ink, the tiles, the law); null in a spawned slot (root only). */
|
|
853
|
+
pass;
|
|
854
|
+
/** The slot's scissor this frame, device px — the layer is laid inside it and it is given back after (null: nothing to lay). */
|
|
855
|
+
within = null;
|
|
856
|
+
/** How many records `prepare` was handed: the whole range the layer lays. */
|
|
857
|
+
handed = 0;
|
|
858
|
+
constructor(pass) {
|
|
859
|
+
this.pass = pass;
|
|
860
|
+
}
|
|
861
|
+
prepare(encoder, s, records) {
|
|
862
|
+
this.within = null;
|
|
863
|
+
this.handed = records.length;
|
|
864
|
+
const p = this.pass;
|
|
865
|
+
if (!p) return 0;
|
|
866
|
+
p.use?.(s.target ?? "frame");
|
|
867
|
+
const n = this.prepareOwn(p, s, records);
|
|
868
|
+
if (n === 0) return 0;
|
|
869
|
+
const size = attachmentOf(s.view);
|
|
870
|
+
if (p.layer(encoder, size, s.view.dpr)) this.within = scissorOf(s.present, s.view.dpr, size);
|
|
871
|
+
return n;
|
|
872
|
+
}
|
|
873
|
+
/** The layer over the frame: once, for the whole range [0, count) — one draw lays every object; a part of it cannot be drawn from one layer. */
|
|
874
|
+
drawRange(pass, first, end) {
|
|
875
|
+
const p = this.pass;
|
|
876
|
+
const within = this.within;
|
|
877
|
+
if (!p || !within || first !== 0 || end < this.handed || end === 0) return;
|
|
878
|
+
const box = p.screenBox;
|
|
879
|
+
if (!box) return;
|
|
880
|
+
const r = clip(box, within);
|
|
881
|
+
if (r[2] <= 0 || r[3] <= 0) return;
|
|
882
|
+
p.composite(pass, r);
|
|
883
|
+
pass.setScissorRect(within[0], within[1], within[2], within[3]);
|
|
884
|
+
}
|
|
885
|
+
endHold() {
|
|
886
|
+
this.pass?.endHold?.();
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Let this slot's layer go when none of its objects was drawn within `ms` (D-K6a.3 for a slot the host keeps beyond the root — the
|
|
890
|
+
* tray's specimen, K5a; the ROOT's layer goes by its kind's own tick). Made again at the next object drawn.
|
|
891
|
+
*/
|
|
892
|
+
idle(ms) {
|
|
893
|
+
const p = this.pass;
|
|
894
|
+
if (p !== null && p.layerMade === true && p.drawnWithin?.(ms) === false) p.releaseLayer?.();
|
|
895
|
+
}
|
|
896
|
+
/** When `idle(ms)` next lets the layer go — its last object drawn + `ms` (K7a: the host's registered TIME wake); ∞ with none. */
|
|
897
|
+
idleAt(ms) {
|
|
898
|
+
const p = this.pass;
|
|
899
|
+
return p !== null && p.layerMade === true && p.lastDrawn !== void 0 ? p.lastDrawn + ms : Number.POSITIVE_INFINITY;
|
|
900
|
+
}
|
|
901
|
+
dropped() {
|
|
902
|
+
return this.pass?.dropped ?? 0;
|
|
903
|
+
}
|
|
904
|
+
dispose() {
|
|
905
|
+
this.pass?.dispose();
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
var BOX_STEP = 256;
|
|
909
|
+
var SIGNATURE = 8;
|
|
910
|
+
var BoxTargets = class {
|
|
911
|
+
/** `view`: the slot's view block (`MatPass.view`) — the composite lays the layer through its portal chain (`layerCompositeLayout`). */
|
|
912
|
+
constructor(device, label, layoutComp, samples, view) {
|
|
913
|
+
this.device = device;
|
|
914
|
+
this.label = label;
|
|
915
|
+
this.layoutComp = layoutComp;
|
|
916
|
+
this.samples = samples;
|
|
917
|
+
this.view = view;
|
|
918
|
+
}
|
|
919
|
+
device;
|
|
920
|
+
label;
|
|
921
|
+
layoutComp;
|
|
922
|
+
samples;
|
|
923
|
+
view;
|
|
924
|
+
msaa = null;
|
|
925
|
+
depth = null;
|
|
926
|
+
resolve = null;
|
|
927
|
+
compGroup = null;
|
|
928
|
+
/** The box's top-left on the attachment, device px (the layer's texel 0, 0). */
|
|
929
|
+
origin = [0, 0];
|
|
930
|
+
/** The targets' size as made, device px (0 × 0: none). */
|
|
931
|
+
size = { w: 0, h: 0 };
|
|
932
|
+
originBuf = null;
|
|
933
|
+
sent = new Float32Array(4).fill(Number.NaN);
|
|
934
|
+
next = new Float32Array(4);
|
|
935
|
+
/** What the layer the targets hold was drawn from — its pass's signature at the draw (K7a); NaN: nothing drawn since made. */
|
|
936
|
+
drawnFrom = new Float64Array(SIGNATURE).fill(Number.NaN);
|
|
937
|
+
/** …and the content it read, by value (the knobs, the records, the view block), with each copy's length. */
|
|
938
|
+
drawnBytes = [];
|
|
939
|
+
drawnLen = [];
|
|
940
|
+
/** The targets are made (a layer holds device memory). */
|
|
941
|
+
get made() {
|
|
942
|
+
return this.msaa !== null;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Fit the targets to `box` [x0, y0, x1, y1] (device px) on an `attach` of its size: made when none are or the box outgrew them,
|
|
946
|
+
* made smaller when they are more than four times its area — never larger than the attachment; the origin written for the
|
|
947
|
+
* composite when it moved.
|
|
948
|
+
*/
|
|
949
|
+
fit(box, attach) {
|
|
950
|
+
const [x0, y0, x1, y1] = box;
|
|
951
|
+
const w = Math.min(Math.ceil(Math.max(1, x1 - x0) / BOX_STEP) * BOX_STEP, Math.max(1, attach.w));
|
|
952
|
+
const h = Math.min(Math.ceil(Math.max(1, y1 - y0) / BOX_STEP) * BOX_STEP, Math.max(1, attach.h));
|
|
953
|
+
const outgrown = this.size.w < x1 - x0 || this.size.h < y1 - y0;
|
|
954
|
+
const loose = this.size.w * this.size.h > 4 * w * h;
|
|
955
|
+
if (this.msaa === null || outgrown || loose) {
|
|
956
|
+
this.release();
|
|
957
|
+
const d = this.device;
|
|
958
|
+
this.msaa = d.createTexture({ label: `${this.label}/layer \xD74`, size: [w, h], format: "rgba8unorm", sampleCount: this.samples, usage: GPUTextureUsage.RENDER_ATTACHMENT });
|
|
959
|
+
this.depth = d.createTexture({ label: `${this.label}/depth \xD74`, size: [w, h], format: "depth24plus", sampleCount: this.samples, usage: GPUTextureUsage.RENDER_ATTACHMENT });
|
|
960
|
+
this.resolve = d.createTexture({ label: `${this.label}/layer`, size: [w, h], format: "rgba8unorm", usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING });
|
|
961
|
+
this.originBuf ??= d.createBuffer({ label: `${this.label}/layer origin`, size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
962
|
+
this.compGroup = d.createBindGroup({ label: `${this.label}/composite`, layout: this.layoutComp, entries: [{ binding: 0, resource: this.resolve.createView() }, { binding: 1, resource: { buffer: this.originBuf } }, { binding: 2, resource: { buffer: this.view } }] });
|
|
963
|
+
this.size.w = w;
|
|
964
|
+
this.size.h = h;
|
|
965
|
+
this.sent.fill(Number.NaN);
|
|
966
|
+
this.drawnFrom.fill(Number.NaN);
|
|
967
|
+
}
|
|
968
|
+
this.origin[0] = x0;
|
|
969
|
+
this.origin[1] = y0;
|
|
970
|
+
const n = this.next;
|
|
971
|
+
n[0] = x0;
|
|
972
|
+
n[1] = y0;
|
|
973
|
+
n[2] = 0;
|
|
974
|
+
n[3] = 0;
|
|
975
|
+
if (n[0] !== this.sent[0] || n[1] !== this.sent[1]) {
|
|
976
|
+
this.device.queue.writeBuffer(this.originBuf, 0, n);
|
|
977
|
+
this.sent.set(n);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Begin the layer's pass into the fitted targets: cleared, the viewport the whole `attach` shifted by the origin, the scissor the
|
|
982
|
+
* box — `box` [x0, y0, x1, y1] as fitted.
|
|
983
|
+
*/
|
|
984
|
+
begin(encoder, label, attach, box) {
|
|
985
|
+
const pass = encoder.beginRenderPass({
|
|
986
|
+
label,
|
|
987
|
+
colorAttachments: [{ view: this.msaa.createView(), resolveTarget: this.resolve.createView(), clearValue: { r: 0, g: 0, b: 0, a: 0 }, loadOp: "clear", storeOp: "discard" }],
|
|
988
|
+
depthStencilAttachment: { view: this.depth.createView(), depthClearValue: 1, depthLoadOp: "clear", depthStoreOp: "discard" }
|
|
989
|
+
});
|
|
990
|
+
pass.setViewport(-box[0], -box[1], attach.w, attach.h, 0, 1);
|
|
991
|
+
pass.setScissorRect(0, 0, box[2] - box[0], box[3] - box[1]);
|
|
992
|
+
return pass;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* The layer the targets hold was drawn from exactly `sig` and `bytes` (K7a — every input of its draw the same since: the pass's
|
|
996
|
+
* numbers — its resources' generation, what it lists, the box — and the CONTENT the draw read, compared by value: this target's
|
|
997
|
+
* knobs and records as prepared, the slot's view block. By content, not by version: a held frame's desk copy writes the shared
|
|
998
|
+
* buffers between two of the hand's frames, and the hand's layer is still what they held): it may be laid again undrawn.
|
|
999
|
+
*/
|
|
1000
|
+
holds(sig, bytes) {
|
|
1001
|
+
if (this.msaa === null) return false;
|
|
1002
|
+
for (let i = 0; i < SIGNATURE; i++) if (this.drawnFrom[i] !== (i < sig.length ? sig[i] : 0)) return false;
|
|
1003
|
+
if (bytes.length !== this.drawnBytes.length) return false;
|
|
1004
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1005
|
+
const a = bytes[i];
|
|
1006
|
+
const b = this.drawnBytes[i];
|
|
1007
|
+
if (this.drawnLen[i] !== a.byteLength) return false;
|
|
1008
|
+
for (let k = 0; k < a.byteLength; k++) if (a[k] !== b[k]) return false;
|
|
1009
|
+
}
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
/** The layer was drawn from `sig` and `bytes` (copied; the copies grow once and are reused). */
|
|
1013
|
+
drew(sig, bytes) {
|
|
1014
|
+
for (let i = 0; i < SIGNATURE; i++) this.drawnFrom[i] = i < sig.length ? sig[i] : 0;
|
|
1015
|
+
this.drawnBytes.length = bytes.length;
|
|
1016
|
+
this.drawnLen.length = bytes.length;
|
|
1017
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1018
|
+
const a = bytes[i];
|
|
1019
|
+
let b = this.drawnBytes[i];
|
|
1020
|
+
if (b === void 0 || b.byteLength < a.byteLength) {
|
|
1021
|
+
b = new Uint8Array(Math.max(a.byteLength, 64));
|
|
1022
|
+
this.drawnBytes[i] = b;
|
|
1023
|
+
}
|
|
1024
|
+
b.set(a);
|
|
1025
|
+
this.drawnLen[i] = a.byteLength;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/** The targets let go, the origin's uniform with them (the next `fit` makes them again). */
|
|
1029
|
+
release() {
|
|
1030
|
+
this.drawnFrom.fill(Number.NaN);
|
|
1031
|
+
this.msaa?.destroy();
|
|
1032
|
+
this.depth?.destroy();
|
|
1033
|
+
this.resolve?.destroy();
|
|
1034
|
+
this.originBuf?.destroy();
|
|
1035
|
+
this.msaa = null;
|
|
1036
|
+
this.depth = null;
|
|
1037
|
+
this.resolve = null;
|
|
1038
|
+
this.compGroup = null;
|
|
1039
|
+
this.originBuf = null;
|
|
1040
|
+
this.size.w = 0;
|
|
1041
|
+
this.size.h = 0;
|
|
1042
|
+
}
|
|
1043
|
+
dispose() {
|
|
1044
|
+
this.release();
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
var LAYER_IDLE_MS = 5e3;
|
|
1048
|
+
|
|
1049
|
+
// ../desk/src/kit/springs.ts
|
|
1050
|
+
function spring(x0, v0, target, hz, damping, dt, substeps = 8) {
|
|
1051
|
+
let x = x0;
|
|
1052
|
+
let v = v0;
|
|
1053
|
+
const w = 2 * Math.PI * hz;
|
|
1054
|
+
const h = Math.min(dt, 0.05) / substeps;
|
|
1055
|
+
for (let i = 0; i < substeps; i++) {
|
|
1056
|
+
v += (w * w * (target - x) - 2 * damping * w * v) * h;
|
|
1057
|
+
x += v * h;
|
|
1058
|
+
}
|
|
1059
|
+
return [x, v];
|
|
1060
|
+
}
|
|
1061
|
+
function settled(x, v, target, eps = 1e-3) {
|
|
1062
|
+
return Math.abs(x - target) < eps && Math.abs(v) < eps * 10;
|
|
1063
|
+
}
|
|
1064
|
+
var SPRINGS = { liftHz: 4.5, liftDamp: 0.78 };
|
|
1065
|
+
|
|
1066
|
+
// ../desk/src/nav/flight.ts
|
|
1067
|
+
function visibleRect(cam, vpW, vpH) {
|
|
1068
|
+
return { x: cam.x, y: cam.y, width: vpW / cam.zoom, height: vpH / cam.zoom };
|
|
1069
|
+
}
|
|
1070
|
+
function portalAffine(arrival, portal) {
|
|
1071
|
+
const s = Math.min(portal.width / arrival.width, portal.height / arrival.height);
|
|
1072
|
+
return {
|
|
1073
|
+
s,
|
|
1074
|
+
ox: portal.x + portal.width / 2 - (arrival.x + arrival.width / 2) * s,
|
|
1075
|
+
oy: portal.y + portal.height / 2 - (arrival.y + arrival.height / 2) * s
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
function solveFlightStart(A, camPre) {
|
|
1079
|
+
return { x: A.ox + camPre.x * A.s, y: A.oy + camPre.y * A.s, zoom: camPre.zoom / A.s };
|
|
1080
|
+
}
|
|
1081
|
+
function outgoingCamera(A, destination) {
|
|
1082
|
+
return { x: (destination.x - A.ox) / A.s, y: (destination.y - A.oy) / A.s, zoom: A.s * destination.zoom };
|
|
1083
|
+
}
|
|
1084
|
+
function flightCamera(c0, c1, p, vpW, vpH) {
|
|
1085
|
+
const zoom = Math.exp(Math.log(c0.zoom) + (Math.log(c1.zoom) - Math.log(c0.zoom)) * p);
|
|
1086
|
+
const inv0 = 1 / c0.zoom;
|
|
1087
|
+
const inv1 = 1 / c1.zoom;
|
|
1088
|
+
const inv = 1 / zoom;
|
|
1089
|
+
const w = Math.abs(inv1 - inv0) < 1e-12 ? p : (inv - inv0) / (inv1 - inv0);
|
|
1090
|
+
const cx = c0.x + vpW * inv0 * 0.5 + (c1.x + vpW * inv1 * 0.5 - (c0.x + vpW * inv0 * 0.5)) * w;
|
|
1091
|
+
const cy = c0.y + vpH * inv0 * 0.5 + (c1.y + vpH * inv1 * 0.5 - (c0.y + vpH * inv0 * 0.5)) * w;
|
|
1092
|
+
return { x: cx - vpW * inv * 0.5, y: cy - vpH * inv * 0.5, zoom };
|
|
1093
|
+
}
|
|
1094
|
+
function fitCamera(content, vpW, vpH, opts) {
|
|
1095
|
+
const fit = Math.min(vpW / (content.width + opts.pad * 2), vpH / (content.height + opts.pad * 2));
|
|
1096
|
+
const zoom = Math.min(opts.maxZoom, Math.max(opts.minZoom, fit));
|
|
1097
|
+
return { x: content.x + content.width / 2 - vpW / (2 * zoom), y: content.y + content.height / 2 - vpH / (2 * zoom), zoom };
|
|
1098
|
+
}
|
|
1099
|
+
var FIT = { pad: 80, minZoom: 0.5, maxZoom: 1 };
|
|
1100
|
+
var clamp01 = (v) => Math.min(1, Math.max(0, v));
|
|
1101
|
+
var smooth = (v) => {
|
|
1102
|
+
const x = clamp01(v);
|
|
1103
|
+
return x * x * (3 - 2 * x);
|
|
1104
|
+
};
|
|
1105
|
+
var ramp = (v, from, to) => to <= from ? v >= to ? 1 : 0 : smooth((v - from) / (to - from));
|
|
1106
|
+
function flightOpacity(kind, p, frozen) {
|
|
1107
|
+
if (frozen) return { outgoing: 1 - ramp(p, 0.05, 0.45), incoming: ramp(p, 0.3, 0.7) };
|
|
1108
|
+
return { outgoing: kind === "enter" ? 1 - ramp(p, 0.3, 0.75) : 1 - ramp(p, 0.85, 1), incoming: 1 };
|
|
1109
|
+
}
|
|
1110
|
+
function boundsOf(rects) {
|
|
1111
|
+
if (rects.length === 0) return null;
|
|
1112
|
+
let x0 = Number.POSITIVE_INFINITY;
|
|
1113
|
+
let y0 = Number.POSITIVE_INFINITY;
|
|
1114
|
+
let x1 = Number.NEGATIVE_INFINITY;
|
|
1115
|
+
let y1 = Number.NEGATIVE_INFINITY;
|
|
1116
|
+
for (const r of rects) {
|
|
1117
|
+
x0 = Math.min(x0, r.x);
|
|
1118
|
+
y0 = Math.min(y0, r.y);
|
|
1119
|
+
x1 = Math.max(x1, r.x + r.width);
|
|
1120
|
+
y1 = Math.max(y1, r.y + r.height);
|
|
1121
|
+
}
|
|
1122
|
+
return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
1123
|
+
}
|
|
1124
|
+
function arrivalCamera(content, vp, fit = FIT) {
|
|
1125
|
+
if (content === null) return { x: -vp.width / 2, y: -vp.height / 2, zoom: 1 };
|
|
1126
|
+
return fitCamera(content, vp.width, vp.height, fit);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// ../desk/src/marks/ease.ts
|
|
1130
|
+
var clamp012 = (x) => Math.min(Math.max(x, 0), 1);
|
|
1131
|
+
function bezier(x1, y1, x2, y2, x) {
|
|
1132
|
+
if (x <= 0) return 0;
|
|
1133
|
+
if (x >= 1) return 1;
|
|
1134
|
+
let t = x;
|
|
1135
|
+
for (let i = 0; i < 8; i++) {
|
|
1136
|
+
const cx = 3 * x1 * t * (1 - t) ** 2 + 3 * x2 * t * t * (1 - t) + t ** 3 - x;
|
|
1137
|
+
const dx = 3 * x1 * (1 - t) ** 2 + 6 * (x2 - x1) * t * (1 - t) + 3 * (1 - x2) * t * t;
|
|
1138
|
+
if (Math.abs(dx) < 1e-6) break;
|
|
1139
|
+
t = clamp012(t - cx / dx);
|
|
1140
|
+
}
|
|
1141
|
+
return 3 * y1 * t * (1 - t) ** 2 + 3 * y2 * t * t * (1 - t) + t ** 3;
|
|
1142
|
+
}
|
|
1143
|
+
var easeLift = (x) => bezier(0.2, 0.9, 0.3, 1.2, x);
|
|
1144
|
+
var easeIsland = (x) => bezier(0.25, 1, 0.3, 1, x);
|
|
1145
|
+
|
|
1146
|
+
// ../desk/src/hold/pose.ts
|
|
1147
|
+
var HOLD = {
|
|
1148
|
+
inMs: 560,
|
|
1149
|
+
outMs: 440,
|
|
1150
|
+
closeLead: 0.14,
|
|
1151
|
+
openAt: 0.42,
|
|
1152
|
+
blur: 14,
|
|
1153
|
+
blurPhone: 10,
|
|
1154
|
+
dim: 0.08,
|
|
1155
|
+
margin: 56,
|
|
1156
|
+
marginPhone: 16,
|
|
1157
|
+
top: 56,
|
|
1158
|
+
topPhone: 60,
|
|
1159
|
+
band: 72,
|
|
1160
|
+
max: 3,
|
|
1161
|
+
zoomMin: 0.72,
|
|
1162
|
+
zoomMax: 3,
|
|
1163
|
+
narrow: 640,
|
|
1164
|
+
/** The reading light by night (Q-n): the held object keeps its day light, filtered. */
|
|
1165
|
+
light: { saturate: 0.62, brightness: 0.82 },
|
|
1166
|
+
/** The cover's spring in hand (desk.js `COVER`): a palm, not a desk. */
|
|
1167
|
+
cover: { hz: 1.4, zeta: 0.78 },
|
|
1168
|
+
/** The held bar's travel (M1 morph) and its return after the landing. */
|
|
1169
|
+
bar: { travelMs: 340, backMs: 200 }
|
|
1170
|
+
};
|
|
1171
|
+
var isNarrow = (vp) => vp.width < HOLD.narrow;
|
|
1172
|
+
function readingTarget(extent, vp, spread = false, face = 0) {
|
|
1173
|
+
const narrow = isNarrow(vp);
|
|
1174
|
+
const m = narrow ? HOLD.marginPhone : HOLD.margin;
|
|
1175
|
+
const top = narrow ? HOLD.topPhone : HOLD.top;
|
|
1176
|
+
const boxW = Math.max(40, vp.width - 2 * m);
|
|
1177
|
+
const boxH = Math.max(40, vp.height - top - HOLD.band);
|
|
1178
|
+
let s = Math.min(boxW / extent.w, boxH / extent.h, HOLD.max);
|
|
1179
|
+
let single = false;
|
|
1180
|
+
if (spread && narrow && vp.width < vp.height * 0.85) {
|
|
1181
|
+
const s1 = Math.min(boxW / (extent.w / 2), boxH / extent.h, HOLD.max);
|
|
1182
|
+
if (s1 > s * 1.3) {
|
|
1183
|
+
s = s1;
|
|
1184
|
+
single = true;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return { cx: vp.width / 2 - (single ? extent.w / 4 * s * (1 - 2 * face) : 0), cy: top + boxH / 2, s, single };
|
|
1188
|
+
}
|
|
1189
|
+
function homePose(extent, angle, cam) {
|
|
1190
|
+
return { cx: (extent.cx - cam.x) * cam.zoom, cy: (extent.cy - cam.y) * cam.zoom, s: cam.zoom, angle };
|
|
1191
|
+
}
|
|
1192
|
+
var HELD_USER_REST = { zoom: 1, panX: 0, panY: 0 };
|
|
1193
|
+
function heldPose(home, target, user, e) {
|
|
1194
|
+
const s1 = target.s * user.zoom;
|
|
1195
|
+
const s = Math.exp(Math.log(Math.max(home.s, 1e-9)) + (Math.log(Math.max(s1, 1e-9)) - Math.log(Math.max(home.s, 1e-9))) * e);
|
|
1196
|
+
return { cx: home.cx + (target.cx + user.panX - home.cx) * e, cy: home.cy + (target.cy + user.panY - home.cy) * e, s, angle: home.angle * (1 - e), e };
|
|
1197
|
+
}
|
|
1198
|
+
var carryOf = (p) => easeIsland(Math.min(Math.max(p, 0), 1));
|
|
1199
|
+
function progressOf(e) {
|
|
1200
|
+
if (e <= 0) return 0;
|
|
1201
|
+
if (e >= 1) return 1;
|
|
1202
|
+
let lo = 0;
|
|
1203
|
+
let hi = 1;
|
|
1204
|
+
for (let i = 0; i < 40; i++) {
|
|
1205
|
+
const mid = (lo + hi) / 2;
|
|
1206
|
+
if (carryOf(mid) < e) lo = mid;
|
|
1207
|
+
else hi = mid;
|
|
1208
|
+
}
|
|
1209
|
+
return (lo + hi) / 2;
|
|
1210
|
+
}
|
|
1211
|
+
function heldFocus(e, vp, theme) {
|
|
1212
|
+
const night = theme.name === "dark";
|
|
1213
|
+
return {
|
|
1214
|
+
blur: isNarrow(vp) ? HOLD.blurPhone : HOLD.blur,
|
|
1215
|
+
dim: HOLD.dim * e,
|
|
1216
|
+
filter: night ? { saturate: 1 - (1 - HOLD.light.saturate) * e, brightness: 1 - (1 - HOLD.light.brightness) * e } : { saturate: 1, brightness: 1 },
|
|
1217
|
+
light: night ? mixLight(theme.matLight, DAY_LIGHT, e) : theme.matLight
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function heldCamera(pose, rect, extent, deskZoom, eye, vp) {
|
|
1221
|
+
const zoom = eye ? Math.min(deskZoom, pose.s) : pose.s;
|
|
1222
|
+
const grow = pose.s / zoom;
|
|
1223
|
+
const ox = rect.cx - extent.cx;
|
|
1224
|
+
const oy = rect.cy - extent.cy;
|
|
1225
|
+
const c = Math.cos(pose.angle);
|
|
1226
|
+
const sn = Math.sin(pose.angle);
|
|
1227
|
+
const sx = pose.cx + (c * ox - sn * oy) * pose.s;
|
|
1228
|
+
const sy = pose.cy + (sn * ox + c * oy) * pose.s;
|
|
1229
|
+
if (!eye || grow <= 1 + 1e-9) return { cam: { x: rect.cx - sx / zoom, y: rect.cy - sy / zoom, zoom }, grow: 1 };
|
|
1230
|
+
const ex = rect.cx - (sx - vp.width / 2) / (grow * zoom);
|
|
1231
|
+
const ey = rect.cy - (sy - vp.height / 2) / (grow * zoom);
|
|
1232
|
+
return { cam: { x: ex - vp.width / (2 * zoom), y: ey - vp.height / (2 * zoom), zoom }, grow };
|
|
1233
|
+
}
|
|
1234
|
+
function heldFrame(pose, extent, single, face = 0) {
|
|
1235
|
+
if (!single) return { cx: pose.cx, cy: pose.cy, hx: extent.w / 2 * pose.s, hy: extent.h / 2 * pose.s, s: pose.s };
|
|
1236
|
+
const half = extent.w / 4 * pose.s;
|
|
1237
|
+
const q = half * (1 - 2 * face);
|
|
1238
|
+
return { cx: pose.cx + q * Math.cos(pose.angle), cy: pose.cy + q * Math.sin(pose.angle), hx: half, hy: extent.h / 2 * pose.s, s: pose.s };
|
|
1239
|
+
}
|
|
1240
|
+
var focusOf = (e, vp) => ({ blur: (isNarrow(vp) ? HOLD.blurPhone : HOLD.blur) * e, dim: HOLD.dim * e });
|
|
1241
|
+
|
|
1242
|
+
// ../desk/src/kit/inside.ts
|
|
1243
|
+
var PAPER_FINISH = "paper";
|
|
1244
|
+
var VINYL_FINISH = "vinyl";
|
|
1245
|
+
function finishOf(finish, drawn) {
|
|
1246
|
+
if (typeof finish === "string") return drawn.includes(finish) ? finish : void 0;
|
|
1247
|
+
return finish.find((f) => drawn.includes(f));
|
|
1248
|
+
}
|
|
1249
|
+
function insideViewOfFace(K, radius, content, cam, vp, fit = FIT, gate = PORTAL_GATE) {
|
|
1250
|
+
if (!(K.width > 0) || !(K.height > 0)) return null;
|
|
1251
|
+
const clip2 = clipOf(K, radius, cam);
|
|
1252
|
+
const arrival = arrivalCamera(content, vp, fit);
|
|
1253
|
+
const M = portalAffine(visibleRect(arrival, vp.width, vp.height), K);
|
|
1254
|
+
return { M, arrival, cam: outgoingCamera(M, cam), clip: clip2, presence: portalPresence(clip2, gate), box: boxOfPortal(clip2, vp) };
|
|
1255
|
+
}
|
|
1256
|
+
var insidePresent = (view) => ({ opacity: 1, objects: view.presence, portal: view.clip });
|
|
1257
|
+
function flightPresent(f, clip2, gate = PORTAL_GATE) {
|
|
1258
|
+
const op = flightOpacity(f.kind, f.p, f.frozen);
|
|
1259
|
+
if (f.frozen || !clip2) return { incoming: { opacity: op.incoming }, outgoing: { opacity: op.outgoing } };
|
|
1260
|
+
if (f.kind === "enter") return { incoming: { opacity: op.incoming, objects: portalPresence(clip2, gate), portal: clip2 }, outgoing: { opacity: op.outgoing } };
|
|
1261
|
+
return { incoming: { opacity: op.incoming }, outgoing: { opacity: op.outgoing, portal: clip2 } };
|
|
1262
|
+
}
|
|
1263
|
+
var HANDOVER = { enter: [0.3, 1], exit: [0, 0.5] };
|
|
1264
|
+
var ease = (p, [a, b]) => {
|
|
1265
|
+
const x = (p - a) / (b - a);
|
|
1266
|
+
return x <= 0 ? 0 : x >= 1 ? 1 : x * x * (3 - 2 * x);
|
|
1267
|
+
};
|
|
1268
|
+
function flightLights(f, cam, outCam) {
|
|
1269
|
+
if (f.frozen) return {};
|
|
1270
|
+
return f.kind === "enter" ? { incoming: { a: outCam, b: cam, t: ease(f.p, HANDOVER.enter) } } : { outgoing: { a: outCam, b: cam, t: ease(f.p, HANDOVER.exit) } };
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// ../desk/src/mat/lamp.ts
|
|
1274
|
+
function lampOf(plane, p = HERO_PROJECTOR) {
|
|
1275
|
+
const m = plane.metresPerUnit;
|
|
1276
|
+
return { x: (p.position[0] - plane.originX) / m, y: (p.position[2] - plane.originZ) / m, h: (p.position[1] - plane.deskY) / m };
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// ../desk/src/kit/services.ts
|
|
1280
|
+
var serviceKey = (name) => ({ name });
|
|
1281
|
+
var service = (key, value) => ({ key, value });
|
|
1282
|
+
function createServices(entries = [], by = "the host") {
|
|
1283
|
+
const lent = /* @__PURE__ */ new Map();
|
|
1284
|
+
const registry = {
|
|
1285
|
+
use: (key) => lent.get(key.name)?.value,
|
|
1286
|
+
lend(more, lender) {
|
|
1287
|
+
for (const { key, value } of more) {
|
|
1288
|
+
const had = lent.get(key.name);
|
|
1289
|
+
if (had !== void 0) throw new Error(`desk: the service "${key.name}" is lent twice \u2014 by ${had.by} and by ${lender} (a name is lent once per desk)`);
|
|
1290
|
+
if (value !== void 0) lent.set(key.name, { value, by: lender });
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
names: () => [...lent.keys()]
|
|
1294
|
+
};
|
|
1295
|
+
registry.lend(entries, by);
|
|
1296
|
+
return registry;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// ../desk/src/kit/blobs.ts
|
|
1300
|
+
var BLOB_STORE = serviceKey("blobs");
|
|
1301
|
+
var PICTURE_DECODER = serviceKey("decode");
|
|
1302
|
+
var RGBA_TYPE = "image/x-ice-rgba";
|
|
1303
|
+
async function hashBytes(bytes) {
|
|
1304
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
1305
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1306
|
+
}
|
|
1307
|
+
function createMemoryBlobStore() {
|
|
1308
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
1309
|
+
return {
|
|
1310
|
+
async put(bytes, type) {
|
|
1311
|
+
const hash2 = await hashBytes(bytes);
|
|
1312
|
+
if (!blobs.has(hash2)) blobs.set(hash2, { bytes: bytes.slice(), type });
|
|
1313
|
+
return hash2;
|
|
1314
|
+
},
|
|
1315
|
+
async get(hash2) {
|
|
1316
|
+
return blobs.get(hash2);
|
|
1317
|
+
},
|
|
1318
|
+
size: () => blobs.size
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// ../desk/src/kit/raster.ts
|
|
1323
|
+
var TEXT_RASTER = serviceKey("text");
|
|
1324
|
+
|
|
1325
|
+
// ../desk/src/kit/text.ts
|
|
1326
|
+
function hashHand(seed, code) {
|
|
1327
|
+
let h = (Math.imul(seed | 0, 2654435761) ^ Math.imul(code | 0, 2246822519)) >>> 0;
|
|
1328
|
+
const out = [];
|
|
1329
|
+
for (let i = 0; i < 4; i++) {
|
|
1330
|
+
h = (h ^ h >>> 16) >>> 0;
|
|
1331
|
+
h = Math.imul(h, 2146121005) >>> 0;
|
|
1332
|
+
h = (h ^ h >>> 15) >>> 0;
|
|
1333
|
+
h = Math.imul(h, 2221713035) >>> 0;
|
|
1334
|
+
h = (h ^ h >>> 16) >>> 0;
|
|
1335
|
+
out.push(h / 4294967296);
|
|
1336
|
+
h = h + 1831565813 >>> 0;
|
|
1337
|
+
}
|
|
1338
|
+
return out;
|
|
1339
|
+
}
|
|
1340
|
+
var glyphSeed = (noteSeed, index) => Math.imul(noteSeed | 0, 668265263) + Math.imul(index + 1, 374761393) | 0;
|
|
1341
|
+
function layoutText(text, box, law, metrics, noteSeed = 1, seeds) {
|
|
1342
|
+
const em = law.size;
|
|
1343
|
+
const lineH = law.lineHeight * em;
|
|
1344
|
+
const x0 = law.pad;
|
|
1345
|
+
const xMax = Math.max(box.w - law.pad, x0 + 1);
|
|
1346
|
+
const yMax = box.h - law.pad;
|
|
1347
|
+
const glyphs = [];
|
|
1348
|
+
const lines = [];
|
|
1349
|
+
const positions = new Float32Array(2 * (text.length + 1));
|
|
1350
|
+
const phase = hashHand(noteSeed, 7)[0] * Math.PI * 2;
|
|
1351
|
+
let x = x0;
|
|
1352
|
+
let y = law.pad + metrics.ascent * em;
|
|
1353
|
+
let line = 0;
|
|
1354
|
+
let lineStart = 0;
|
|
1355
|
+
let prev = "";
|
|
1356
|
+
let overflow = false;
|
|
1357
|
+
const adv = (ch) => metrics.advance(ch) * em;
|
|
1358
|
+
const newline = (i) => {
|
|
1359
|
+
lines.push({ start: lineStart, end: i, y, width: x - x0 });
|
|
1360
|
+
lineStart = i;
|
|
1361
|
+
line += 1;
|
|
1362
|
+
x = x0;
|
|
1363
|
+
y += lineH;
|
|
1364
|
+
prev = "";
|
|
1365
|
+
};
|
|
1366
|
+
const wordWidth = (i) => {
|
|
1367
|
+
let w = 0;
|
|
1368
|
+
let p = "";
|
|
1369
|
+
for (let j = i; j < text.length; j++) {
|
|
1370
|
+
const c = text[j];
|
|
1371
|
+
if (c === " " || c === "\n") break;
|
|
1372
|
+
if (p && metrics.kern) w += metrics.kern(p, c) * em;
|
|
1373
|
+
w += adv(c);
|
|
1374
|
+
p = c;
|
|
1375
|
+
}
|
|
1376
|
+
return w;
|
|
1377
|
+
};
|
|
1378
|
+
for (let i = 0; i < text.length; i++) {
|
|
1379
|
+
const ch = text[i];
|
|
1380
|
+
if (ch === "\n") {
|
|
1381
|
+
positions[2 * i] = x;
|
|
1382
|
+
positions[2 * i + 1] = y;
|
|
1383
|
+
newline(i + 1);
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
const wordStart = i === 0 || text[i - 1] === " " || text[i - 1] === "\n";
|
|
1387
|
+
if (ch !== " " && wordStart && x > x0 && x + wordWidth(i) > xMax) newline(i);
|
|
1388
|
+
if (ch !== " " && !wordStart && prev && metrics.kern) x += metrics.kern(prev, ch) * em;
|
|
1389
|
+
if (ch !== " " && x > x0 && x + adv(ch) > xMax) newline(i);
|
|
1390
|
+
positions[2 * i] = x;
|
|
1391
|
+
positions[2 * i + 1] = y;
|
|
1392
|
+
if (ch === " ") {
|
|
1393
|
+
x += adv(ch);
|
|
1394
|
+
prev = ch;
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
const seed = seeds && i < seeds.length ? seeds[i] : glyphSeed(noteSeed, i);
|
|
1398
|
+
const [r0, r1, r2, r3] = hashHand(seed, ch.codePointAt(0) ?? 0);
|
|
1399
|
+
const scale = 1 + (r2 - 0.5) * 2 * law.jitter.scale;
|
|
1400
|
+
const a = adv(ch) * scale;
|
|
1401
|
+
const wander = law.wander.amp * em * Math.sin(2 * Math.PI * (x - x0) / Math.max(law.wander.period * em, 1e-6) + phase);
|
|
1402
|
+
glyphs.push({
|
|
1403
|
+
ch,
|
|
1404
|
+
index: i,
|
|
1405
|
+
x,
|
|
1406
|
+
y: y + wander + (r1 - 0.5) * 2 * law.jitter.rise * em,
|
|
1407
|
+
advance: a,
|
|
1408
|
+
rot: (r0 - 0.5) * 2 * law.jitter.rot,
|
|
1409
|
+
scale,
|
|
1410
|
+
press: law.jitter.press[0] + (law.jitter.press[1] - law.jitter.press[0]) * r3,
|
|
1411
|
+
line
|
|
1412
|
+
});
|
|
1413
|
+
if (y + metrics.descent * em > yMax) overflow = true;
|
|
1414
|
+
x += a;
|
|
1415
|
+
prev = ch;
|
|
1416
|
+
}
|
|
1417
|
+
positions[2 * text.length] = x;
|
|
1418
|
+
positions[2 * text.length + 1] = y;
|
|
1419
|
+
lines.push({ start: lineStart, end: text.length, y, width: x - x0 });
|
|
1420
|
+
if (y + metrics.descent * em > yMax && text.length > 0) overflow = true;
|
|
1421
|
+
return { glyphs, lines, positions, overflow, em, ascent: metrics.ascent * em, descent: metrics.descent * em };
|
|
1422
|
+
}
|
|
1423
|
+
function caretAt(L, index) {
|
|
1424
|
+
const i = Math.min(Math.max(index, 0), L.positions.length / 2 - 1);
|
|
1425
|
+
return { x: L.positions[2 * i], y: L.positions[2 * i + 1], above: L.ascent, below: L.descent };
|
|
1426
|
+
}
|
|
1427
|
+
function glyphBox(L, g) {
|
|
1428
|
+
const grow = L.em * 0.12;
|
|
1429
|
+
return { x0: g.x - grow, y0: g.y - L.ascent * g.scale - grow, x1: g.x + g.advance + grow, y1: g.y + L.descent * g.scale + grow };
|
|
1430
|
+
}
|
|
1431
|
+
function carrySeeds(oldText, oldSeeds, newText, fresh) {
|
|
1432
|
+
let pre = 0;
|
|
1433
|
+
const maxPre = Math.min(oldText.length, newText.length);
|
|
1434
|
+
while (pre < maxPre && oldText[pre] === newText[pre]) pre += 1;
|
|
1435
|
+
let suf = 0;
|
|
1436
|
+
const maxSuf = Math.min(oldText.length, newText.length) - pre;
|
|
1437
|
+
while (suf < maxSuf && oldText[oldText.length - 1 - suf] === newText[newText.length - 1 - suf]) suf += 1;
|
|
1438
|
+
const seeds = [];
|
|
1439
|
+
for (let i = 0; i < pre; i++) seeds.push(i < oldSeeds.length ? oldSeeds[i] : fresh());
|
|
1440
|
+
const inserted = newText.length - pre - suf;
|
|
1441
|
+
for (let i = 0; i < inserted; i++) seeds.push(fresh());
|
|
1442
|
+
for (let i = 0; i < suf; i++) {
|
|
1443
|
+
const j = oldText.length - suf + i;
|
|
1444
|
+
seeds.push(j < oldSeeds.length ? oldSeeds[j] : fresh());
|
|
1445
|
+
}
|
|
1446
|
+
return { seeds, from: pre, to: pre + inserted };
|
|
1447
|
+
}
|
|
1448
|
+
var HAND = {
|
|
1449
|
+
size: 24,
|
|
1450
|
+
lineHeight: 1.16,
|
|
1451
|
+
pad: 16,
|
|
1452
|
+
jitter: { rot: 0.028, rise: 0.03, scale: 0.05, press: [0.8, 0.97] },
|
|
1453
|
+
wander: { amp: 0.022, period: 9 },
|
|
1454
|
+
wipeMs: 110
|
|
1455
|
+
};
|
|
1456
|
+
var PENS = ["felt", "ball", "fountain", "red"];
|
|
1457
|
+
var PEN_FACES = {
|
|
1458
|
+
caveat: { family: "Caveat", weight: 500 },
|
|
1459
|
+
"caveat-bold": { family: "Caveat", weight: 600 },
|
|
1460
|
+
kalam: { family: "Kalam", weight: 400 },
|
|
1461
|
+
marker: { family: "Marker Felt", weight: 400 },
|
|
1462
|
+
noteworthy: { family: "Noteworthy", weight: 400 }
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// ../desk/src/kit/arrays.ts
|
|
1466
|
+
function tailBase(w, h, side) {
|
|
1467
|
+
let k = 0;
|
|
1468
|
+
while (Math.max(w >> k, 1) > side || Math.max(h >> k, 1) > side) k += 1;
|
|
1469
|
+
return k;
|
|
1470
|
+
}
|
|
1471
|
+
function chainBytes(w, h, levels, texel = 4) {
|
|
1472
|
+
let b = 0;
|
|
1473
|
+
for (let l = 0; l < levels; l++) b += Math.max(w >> l, 1) * Math.max(h >> l, 1) * texel;
|
|
1474
|
+
return b;
|
|
1475
|
+
}
|
|
1476
|
+
var Layers = class {
|
|
1477
|
+
constructor(first = 4, max = 256) {
|
|
1478
|
+
this.first = first;
|
|
1479
|
+
this.max = max;
|
|
1480
|
+
}
|
|
1481
|
+
first;
|
|
1482
|
+
max;
|
|
1483
|
+
free = [];
|
|
1484
|
+
next = 0;
|
|
1485
|
+
capacity = 0;
|
|
1486
|
+
take() {
|
|
1487
|
+
if (this.free.length > 0) {
|
|
1488
|
+
this.free.sort((a, b) => a - b);
|
|
1489
|
+
return { layer: this.free.shift(), capacity: this.capacity };
|
|
1490
|
+
}
|
|
1491
|
+
if (this.next >= this.max) return null;
|
|
1492
|
+
const layer = this.next++;
|
|
1493
|
+
if (layer >= this.capacity) this.capacity = Math.min(Math.max(this.first, this.capacity * 2), this.max);
|
|
1494
|
+
return { layer, capacity: this.capacity };
|
|
1495
|
+
}
|
|
1496
|
+
give(layer) {
|
|
1497
|
+
if (layer >= 0 && layer < this.next && !this.free.includes(layer)) this.free.push(layer);
|
|
1498
|
+
}
|
|
1499
|
+
get used() {
|
|
1500
|
+
return this.next - this.free.length;
|
|
1501
|
+
}
|
|
1502
|
+
/** The highest layer in use (−1 when none): what a smaller array must still hold. */
|
|
1503
|
+
get highest() {
|
|
1504
|
+
let h = this.next - 1;
|
|
1505
|
+
while (h >= 0 && this.free.includes(h)) h -= 1;
|
|
1506
|
+
return h;
|
|
1507
|
+
}
|
|
1508
|
+
/**
|
|
1509
|
+
* The capacity an array holding these layers may shrink to (K9 R2): halved while fewer than a QUARTER of it is in use and the
|
|
1510
|
+
* highest layer in use fits the half — down to `first`; 0 when none is in use; the current capacity when it cannot shrink.
|
|
1511
|
+
*/
|
|
1512
|
+
shrunk() {
|
|
1513
|
+
if (this.used === 0) return 0;
|
|
1514
|
+
let c = this.capacity;
|
|
1515
|
+
while (c > this.first && this.used * 4 < c && this.highest < c / 2) c /= 2;
|
|
1516
|
+
return c;
|
|
1517
|
+
}
|
|
1518
|
+
/** The allocator's word after the array was remade at `capacity` (from `shrunk()`): the layers past it are forgotten. */
|
|
1519
|
+
shrinkTo(capacity) {
|
|
1520
|
+
if (capacity === 0) {
|
|
1521
|
+
this.free = [];
|
|
1522
|
+
this.next = 0;
|
|
1523
|
+
this.capacity = 0;
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
this.next = Math.min(this.next, capacity);
|
|
1527
|
+
this.free = this.free.filter((l) => l < this.next);
|
|
1528
|
+
this.capacity = capacity;
|
|
1529
|
+
}
|
|
1530
|
+
};
|
|
1531
|
+
var FILL = (
|
|
1532
|
+
/* wgsl */
|
|
1533
|
+
`
|
|
1534
|
+
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
1535
|
+
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
|
|
1536
|
+
let p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0))[i];
|
|
1537
|
+
return vec4f(p, 0.0, 1.0);
|
|
1538
|
+
}
|
|
1539
|
+
@fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
|
|
1540
|
+
let last = vec2i(textureDimensions(src)) - vec2i(1);
|
|
1541
|
+
return textureLoad(src, min(vec2i(pos.xy), last), 0);
|
|
1542
|
+
}
|
|
1543
|
+
`
|
|
1544
|
+
);
|
|
1545
|
+
var plainFormat = (f) => f.endsWith("-srgb") ? f.slice(0, -5) : f;
|
|
1546
|
+
var LayerArray = class {
|
|
1547
|
+
side;
|
|
1548
|
+
mips;
|
|
1549
|
+
format;
|
|
1550
|
+
device;
|
|
1551
|
+
label;
|
|
1552
|
+
layers;
|
|
1553
|
+
tex = null;
|
|
1554
|
+
/** The texture's layers (kept here: a fake device's texture has no count of its own). */
|
|
1555
|
+
cap = 0;
|
|
1556
|
+
/** Bumped whenever the texture is remade (a bind group holding its view must be made again). */
|
|
1557
|
+
version = 0;
|
|
1558
|
+
/** The fill's pipeline, made on the first fill — labelled with the array's own name, so the profiler counts its passes as the kind's. */
|
|
1559
|
+
fillPipe = null;
|
|
1560
|
+
constructor(device, opts) {
|
|
1561
|
+
this.device = device;
|
|
1562
|
+
this.label = opts.label;
|
|
1563
|
+
this.format = opts.format;
|
|
1564
|
+
this.side = opts.side;
|
|
1565
|
+
this.mips = Math.floor(Math.log2(opts.side)) + 1;
|
|
1566
|
+
const max = device.limits?.maxTextureArrayLayers ?? 256;
|
|
1567
|
+
this.layers = new Layers(opts.first ?? 4, max);
|
|
1568
|
+
}
|
|
1569
|
+
/** The array (null before its first layer). */
|
|
1570
|
+
get texture() {
|
|
1571
|
+
return this.tex;
|
|
1572
|
+
}
|
|
1573
|
+
/** Layers in use; the array's capacity. */
|
|
1574
|
+
get used() {
|
|
1575
|
+
return this.layers.used;
|
|
1576
|
+
}
|
|
1577
|
+
get capacity() {
|
|
1578
|
+
return this.tex === null ? 0 : this.layers.capacity;
|
|
1579
|
+
}
|
|
1580
|
+
/** What the array weighs on the device, every layer of its capacity with its chain (the GPU ledger's row). */
|
|
1581
|
+
get bytes() {
|
|
1582
|
+
return this.capacity * chainBytes(this.side, this.side, this.mips);
|
|
1583
|
+
}
|
|
1584
|
+
/** What the layers IN USE weigh (K9 R2: the budget's resident charge — the capacity's slack is the ledger's to show, bounded by `give`'s shrink). */
|
|
1585
|
+
get usedBytes() {
|
|
1586
|
+
return this.used * chainBytes(this.side, this.side, this.mips);
|
|
1587
|
+
}
|
|
1588
|
+
/** A layer for a new object (the array grown — its old layers copied — when it must be); null when the device holds no more. */
|
|
1589
|
+
take() {
|
|
1590
|
+
const t = this.layers.take();
|
|
1591
|
+
if (t === null) return null;
|
|
1592
|
+
if (this.tex === null || t.capacity > this.cap) this.remake(t.capacity);
|
|
1593
|
+
return t.layer;
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* A layer given back — and the array SHRUNK when fewer than a quarter of its layers are in use and the ones in use fit the
|
|
1597
|
+
* half (K9 R2: an array never shrank before, so its charge survived a document switch): remade at half, its low layers copied;
|
|
1598
|
+
* let go entirely when none is in use. The version moves as on a growth — a holder rebinds its view.
|
|
1599
|
+
*/
|
|
1600
|
+
give(layer) {
|
|
1601
|
+
this.layers.give(layer);
|
|
1602
|
+
if (this.tex === null) return;
|
|
1603
|
+
const to = this.layers.shrunk();
|
|
1604
|
+
if (to >= this.cap) return;
|
|
1605
|
+
if (to === 0) {
|
|
1606
|
+
this.tex.destroy();
|
|
1607
|
+
this.tex = null;
|
|
1608
|
+
this.cap = 0;
|
|
1609
|
+
this.layers.shrinkTo(0);
|
|
1610
|
+
this.version += 1;
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
this.layers.shrinkTo(to);
|
|
1614
|
+
this.remake(to);
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Layer `layer`'s every level from `src`'s chain: level `base + m` at the origin, its edge carried to the layer's; a level past
|
|
1618
|
+
* the chain's end takes its last. `src` must allow the plain view of its format (`viewFormats`) when that format is `-srgb`.
|
|
1619
|
+
*/
|
|
1620
|
+
fill(src, srcMips, layer, base, encoder) {
|
|
1621
|
+
const tex = this.tex;
|
|
1622
|
+
if (tex === null) return;
|
|
1623
|
+
const plain = plainFormat(this.format);
|
|
1624
|
+
const pipe = this.pipeline(plain);
|
|
1625
|
+
const enc = encoder ?? this.device.createCommandEncoder({ label: `${this.label} fill` });
|
|
1626
|
+
for (let m = 0; m < this.mips; m++) {
|
|
1627
|
+
const from = src.createView({ format: plainFormat(src.format ?? this.format), dimension: "2d", baseMipLevel: Math.min(base + m, srcMips - 1), mipLevelCount: 1 });
|
|
1628
|
+
const to = tex.createView({ format: plain, dimension: "2d", baseArrayLayer: layer, arrayLayerCount: 1, baseMipLevel: m, mipLevelCount: 1 });
|
|
1629
|
+
const group = this.device.createBindGroup({ label: `${this.label} fill`, layout: pipe.getBindGroupLayout(0), entries: [{ binding: 0, resource: from }] });
|
|
1630
|
+
const pass = enc.beginRenderPass({ label: `${this.label} fill`, colorAttachments: [{ view: to, loadOp: "clear", storeOp: "store", clearValue: { r: 0, g: 0, b: 0, a: 0 } }] });
|
|
1631
|
+
pass.setPipeline(pipe);
|
|
1632
|
+
pass.setBindGroup(0, group);
|
|
1633
|
+
pass.draw(3);
|
|
1634
|
+
pass.end();
|
|
1635
|
+
}
|
|
1636
|
+
if (encoder === void 0) this.device.queue.submit([enc.finish()]);
|
|
1637
|
+
}
|
|
1638
|
+
/** A `2d-array` view of the whole array — or of `blank` (a 1 × 1 × 1 stand-in the caller keeps) before its first layer. */
|
|
1639
|
+
view(blank) {
|
|
1640
|
+
return (this.tex ?? blank).createView({ dimension: "2d-array" });
|
|
1641
|
+
}
|
|
1642
|
+
destroy() {
|
|
1643
|
+
this.tex?.destroy();
|
|
1644
|
+
this.tex = null;
|
|
1645
|
+
this.cap = 0;
|
|
1646
|
+
}
|
|
1647
|
+
/** The array made again at `capacity` layers — the old one's layers copied, as many as both hold — and the old destroyed. */
|
|
1648
|
+
remake(capacity) {
|
|
1649
|
+
const next = this.device.createTexture({
|
|
1650
|
+
label: this.label,
|
|
1651
|
+
size: [this.side, this.side, capacity],
|
|
1652
|
+
format: this.format,
|
|
1653
|
+
mipLevelCount: this.mips,
|
|
1654
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1655
|
+
...plainFormat(this.format) !== this.format ? { viewFormats: [plainFormat(this.format)] } : {}
|
|
1656
|
+
});
|
|
1657
|
+
const old = this.tex;
|
|
1658
|
+
if (old !== null) {
|
|
1659
|
+
const enc = this.device.createCommandEncoder({ label: `${this.label} ${capacity > this.cap ? "grow" : "shrink"}` });
|
|
1660
|
+
const layers = Math.min(this.cap, capacity);
|
|
1661
|
+
for (let m = 0; m < this.mips; m++) {
|
|
1662
|
+
const s = Math.max(this.side >> m, 1);
|
|
1663
|
+
enc.copyTextureToTexture({ texture: old, mipLevel: m }, { texture: next, mipLevel: m }, [s, s, layers]);
|
|
1664
|
+
}
|
|
1665
|
+
this.device.queue.submit([enc.finish()]);
|
|
1666
|
+
old.destroy();
|
|
1667
|
+
}
|
|
1668
|
+
this.tex = next;
|
|
1669
|
+
this.cap = capacity;
|
|
1670
|
+
this.version += 1;
|
|
1671
|
+
}
|
|
1672
|
+
pipeline(format) {
|
|
1673
|
+
if (this.fillPipe === null) {
|
|
1674
|
+
const module = this.device.createShaderModule({ label: `${this.label} fill`, code: FILL });
|
|
1675
|
+
this.fillPipe = this.device.createRenderPipeline({ label: `${this.label} fill`, layout: "auto", vertex: { module, entryPoint: "vs" }, fragment: { module, entryPoint: "fs", targets: [{ format }] } });
|
|
1676
|
+
}
|
|
1677
|
+
return this.fillPipe;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// ../desk/src/kit/eye.ts
|
|
1682
|
+
function eyeOf(cam, vp, law) {
|
|
1683
|
+
const zoom = Math.max(cam.zoom, 1e-9);
|
|
1684
|
+
const diag = Math.hypot(vp.width, vp.height) / zoom;
|
|
1685
|
+
return { ex: cam.x + vp.width / (2 * zoom), ey: cam.y + vp.height / (2 * zoom), h: Math.max(law.min, law.k * diag), zoom, vw: vp.width, vh: vp.height };
|
|
1686
|
+
}
|
|
1687
|
+
function project(e, x, y, z) {
|
|
1688
|
+
const k = e.h / Math.max(e.h - z, 1e-6);
|
|
1689
|
+
return [e.vw / 2 + (x - e.ex) * k * e.zoom, e.vh / 2 + (y - e.ey) * k * e.zoom];
|
|
1690
|
+
}
|
|
1691
|
+
function deskAt(e, sx, sy) {
|
|
1692
|
+
return [e.ex + (sx - e.vw / 2) / e.zoom, e.ey + (sy - e.vh / 2) / e.zoom];
|
|
1693
|
+
}
|
|
1694
|
+
function unproject(e, dx, dy, z) {
|
|
1695
|
+
const f = (e.h - z) / e.h;
|
|
1696
|
+
return [e.ex + (dx - e.ex) * f, e.ey + (dy - e.ey) * f];
|
|
1697
|
+
}
|
|
1698
|
+
function rayThrough(e, dx, dy) {
|
|
1699
|
+
return { o: [e.ex, e.ey, e.h], d: [dx - e.ex, dy - e.ey, -e.h] };
|
|
1700
|
+
}
|
|
1701
|
+
function eyeValues(e, reach = 900) {
|
|
1702
|
+
const near = Math.max(e.h - reach, 1);
|
|
1703
|
+
const far = e.h + 40;
|
|
1704
|
+
return { eye: [e.ex, e.ey, e.h, e.zoom], view: [e.vw, e.vh, near, far] };
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// ../desk/src/kit/mesh.ts
|
|
1708
|
+
var VERTEX_FLOATS = 16;
|
|
1709
|
+
var VERTEX_BYTES = VERTEX_FLOATS * 4;
|
|
1710
|
+
var FLAG_TWO_SIDED = 1;
|
|
1711
|
+
var MeshWriter = class {
|
|
1712
|
+
v;
|
|
1713
|
+
ix;
|
|
1714
|
+
nv = 0;
|
|
1715
|
+
ni = 0;
|
|
1716
|
+
min = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
|
|
1717
|
+
max = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
|
1718
|
+
constructor(vcap = 8192, icap = 24576) {
|
|
1719
|
+
this.v = new Float32Array(vcap * VERTEX_FLOATS);
|
|
1720
|
+
this.ix = new Uint32Array(icap);
|
|
1721
|
+
}
|
|
1722
|
+
reset() {
|
|
1723
|
+
this.nv = 0;
|
|
1724
|
+
this.ni = 0;
|
|
1725
|
+
this.min.fill(Number.POSITIVE_INFINITY);
|
|
1726
|
+
this.max.fill(Number.NEGATIVE_INFINITY);
|
|
1727
|
+
}
|
|
1728
|
+
vert(px, py, pz, nx, ny, nz, u, v, mat, page, flags, ao, sx, sy, e0 = 0, e1 = 0) {
|
|
1729
|
+
if ((this.nv + 1) * VERTEX_FLOATS > this.v.length) {
|
|
1730
|
+
const g = new Float32Array(this.v.length * 2);
|
|
1731
|
+
g.set(this.v);
|
|
1732
|
+
this.v = g;
|
|
1733
|
+
}
|
|
1734
|
+
const o = this.nv * VERTEX_FLOATS;
|
|
1735
|
+
const a = this.v;
|
|
1736
|
+
const nl = Math.hypot(nx, ny, nz) || 1;
|
|
1737
|
+
a[o] = px;
|
|
1738
|
+
a[o + 1] = py;
|
|
1739
|
+
a[o + 2] = pz;
|
|
1740
|
+
a[o + 3] = nx / nl;
|
|
1741
|
+
a[o + 4] = ny / nl;
|
|
1742
|
+
a[o + 5] = nz / nl;
|
|
1743
|
+
a[o + 6] = u;
|
|
1744
|
+
a[o + 7] = v;
|
|
1745
|
+
a[o + 8] = mat;
|
|
1746
|
+
a[o + 9] = page;
|
|
1747
|
+
a[o + 10] = flags;
|
|
1748
|
+
a[o + 11] = ao;
|
|
1749
|
+
a[o + 12] = sx;
|
|
1750
|
+
a[o + 13] = sy;
|
|
1751
|
+
a[o + 14] = e0;
|
|
1752
|
+
a[o + 15] = e1;
|
|
1753
|
+
if (px < this.min[0]) this.min[0] = px;
|
|
1754
|
+
if (py < this.min[1]) this.min[1] = py;
|
|
1755
|
+
if (pz < this.min[2]) this.min[2] = pz;
|
|
1756
|
+
if (px > this.max[0]) this.max[0] = px;
|
|
1757
|
+
if (py > this.max[1]) this.max[1] = py;
|
|
1758
|
+
if (pz > this.max[2]) this.max[2] = pz;
|
|
1759
|
+
return this.nv++;
|
|
1760
|
+
}
|
|
1761
|
+
tri(a, b, c) {
|
|
1762
|
+
if (this.ni + 3 > this.ix.length) {
|
|
1763
|
+
const g = new Uint32Array(this.ix.length * 2);
|
|
1764
|
+
g.set(this.ix);
|
|
1765
|
+
this.ix = g;
|
|
1766
|
+
}
|
|
1767
|
+
this.ix[this.ni++] = a;
|
|
1768
|
+
this.ix[this.ni++] = b;
|
|
1769
|
+
this.ix[this.ni++] = c;
|
|
1770
|
+
}
|
|
1771
|
+
quad(a, b, c, d) {
|
|
1772
|
+
this.tri(a, b, c);
|
|
1773
|
+
this.tri(a, c, d);
|
|
1774
|
+
}
|
|
1775
|
+
/** A grid's quads: `cols` × `rows` vertices laid out row-major from `first`. */
|
|
1776
|
+
gridQuads(first, cols, rows) {
|
|
1777
|
+
for (let j = 0; j + 1 < rows; j++) for (let i = 0; i + 1 < cols; i++) {
|
|
1778
|
+
const a = first + j * cols + i;
|
|
1779
|
+
this.quad(a, a + 1, a + cols + 1, a + cols);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
// ../desk/src/kit/mips.ts
|
|
1785
|
+
var WGSL2 = (
|
|
1786
|
+
/* wgsl */
|
|
1787
|
+
`
|
|
1788
|
+
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
1789
|
+
@group(0) @binding(1) var samp: sampler;
|
|
1790
|
+
struct VO { @builtin(position) pos: vec4f, @location(0) uv: vec2f }
|
|
1791
|
+
@vertex fn vs(@builtin(vertex_index) i: u32) -> VO {
|
|
1792
|
+
let p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0))[i];
|
|
1793
|
+
var o: VO; o.pos = vec4f(p, 0.0, 1.0); o.uv = vec2f(0.5 * p.x + 0.5, 0.5 - 0.5 * p.y); return o;
|
|
1794
|
+
}
|
|
1795
|
+
@fragment fn fs(v: VO) -> @location(0) vec4f { return textureSampleLevel(src, samp, v.uv, 0.0); }
|
|
1796
|
+
`
|
|
1797
|
+
);
|
|
1798
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
1799
|
+
var mipCount = (w, h) => Math.floor(Math.log2(Math.max(w, h, 1))) + 1;
|
|
1800
|
+
function generateMips(device, texture) {
|
|
1801
|
+
let c = cache.get(device);
|
|
1802
|
+
if (!c) {
|
|
1803
|
+
c = { module: device.createShaderModule({ label: "photo/mips", code: WGSL2 }), sampler: device.createSampler({ minFilter: "linear", magFilter: "linear" }), pipes: /* @__PURE__ */ new Map() };
|
|
1804
|
+
cache.set(device, c);
|
|
1805
|
+
}
|
|
1806
|
+
let pipe = c.pipes.get(texture.format);
|
|
1807
|
+
if (!pipe) {
|
|
1808
|
+
pipe = device.createRenderPipeline({
|
|
1809
|
+
label: `photo/mips ${texture.format}`,
|
|
1810
|
+
layout: "auto",
|
|
1811
|
+
vertex: { module: c.module, entryPoint: "vs" },
|
|
1812
|
+
fragment: { module: c.module, entryPoint: "fs", targets: [{ format: texture.format }] }
|
|
1813
|
+
});
|
|
1814
|
+
c.pipes.set(texture.format, pipe);
|
|
1815
|
+
}
|
|
1816
|
+
const encoder = device.createCommandEncoder({ label: "photo/mips" });
|
|
1817
|
+
for (let level = 1; level < texture.mipLevelCount; level++) {
|
|
1818
|
+
const src = texture.createView({ baseMipLevel: level - 1, mipLevelCount: 1 });
|
|
1819
|
+
const dst = texture.createView({ baseMipLevel: level, mipLevelCount: 1 });
|
|
1820
|
+
const group = device.createBindGroup({ layout: pipe.getBindGroupLayout(0), entries: [{ binding: 0, resource: src }, { binding: 1, resource: c.sampler }] });
|
|
1821
|
+
const pass = encoder.beginRenderPass({ colorAttachments: [{ view: dst, loadOp: "clear", storeOp: "store", clearValue: { r: 0, g: 0, b: 0, a: 0 } }] });
|
|
1822
|
+
pass.setPipeline(pipe);
|
|
1823
|
+
pass.setBindGroup(0, group);
|
|
1824
|
+
pass.draw(3);
|
|
1825
|
+
pass.end();
|
|
1826
|
+
}
|
|
1827
|
+
device.queue.submit([encoder.finish()]);
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// ../desk/src/kit/paper-tex.ts
|
|
1831
|
+
var PAPER_TEX = 256;
|
|
1832
|
+
var TOOTH_SCALE = 48;
|
|
1833
|
+
var fract = (x) => x - Math.floor(x);
|
|
1834
|
+
function hash(i, j, seed) {
|
|
1835
|
+
let h = (Math.imul(i, 668265261) ^ Math.imul(j, 374761393) ^ Math.imul(seed, 1274126177)) >>> 0;
|
|
1836
|
+
h = Math.imul(h ^ h >>> 15, 2246822519) >>> 0;
|
|
1837
|
+
h = Math.imul(h ^ h >>> 13, 3266489917) >>> 0;
|
|
1838
|
+
return ((h ^ h >>> 16) >>> 0) / 4294967296;
|
|
1839
|
+
}
|
|
1840
|
+
function vnoise(u, v, cells2, seed) {
|
|
1841
|
+
const x = u * cells2;
|
|
1842
|
+
const y = v * cells2;
|
|
1843
|
+
const i = Math.floor(x);
|
|
1844
|
+
const j = Math.floor(y);
|
|
1845
|
+
const fx = x - i;
|
|
1846
|
+
const fy = y - j;
|
|
1847
|
+
const w = (t) => t * t * (3 - 2 * t);
|
|
1848
|
+
const dw = (t) => 6 * t * (1 - t);
|
|
1849
|
+
const at = (a2, b2) => hash((a2 % cells2 + cells2) % cells2, (b2 % cells2 + cells2) % cells2, seed);
|
|
1850
|
+
const a = at(i, j);
|
|
1851
|
+
const b = at(i + 1, j);
|
|
1852
|
+
const c = at(i, j + 1);
|
|
1853
|
+
const d = at(i + 1, j + 1);
|
|
1854
|
+
const k = a - b - c + d;
|
|
1855
|
+
const wx = w(fx);
|
|
1856
|
+
const wy = w(fy);
|
|
1857
|
+
return [a + (b - a) * wx + (c - a) * wy + k * wx * wy - 0.5, dw(fx) * (b - a + k * wy) * cells2, dw(fy) * (c - a + k * wx) * cells2];
|
|
1858
|
+
}
|
|
1859
|
+
function paperTexture(seed = 7) {
|
|
1860
|
+
const N = PAPER_TEX;
|
|
1861
|
+
const out = new Uint8Array(N * N * 4);
|
|
1862
|
+
const fib = new Float32Array(N * N);
|
|
1863
|
+
let r = seed * 9973;
|
|
1864
|
+
const rnd = () => {
|
|
1865
|
+
r = Math.imul(r, 1664525) + 1013904223 >>> 0;
|
|
1866
|
+
return r / 4294967296;
|
|
1867
|
+
};
|
|
1868
|
+
for (let f = 0; f < 420; f++) {
|
|
1869
|
+
const x0 = rnd() * N;
|
|
1870
|
+
const y0 = rnd() * N;
|
|
1871
|
+
const a = rnd() < 0.65 ? Math.PI / 2 + (rnd() - 0.5) * 0.9 : rnd() * Math.PI;
|
|
1872
|
+
const len = 5 + rnd() * 16;
|
|
1873
|
+
const bend = (rnd() - 0.5) * 0.12;
|
|
1874
|
+
const tone = (rnd() < 0.55 ? 1 : -1) * (0.35 + rnd() * 0.65);
|
|
1875
|
+
const wid = 0.45 + rnd() * 0.5;
|
|
1876
|
+
let x = x0;
|
|
1877
|
+
let y = y0;
|
|
1878
|
+
let ang = a;
|
|
1879
|
+
for (let s = 0; s < len; s += 0.5) {
|
|
1880
|
+
x += Math.cos(ang) * 0.5;
|
|
1881
|
+
y += Math.sin(ang) * 0.5;
|
|
1882
|
+
ang += bend;
|
|
1883
|
+
const fade = Math.sin(s / len * Math.PI);
|
|
1884
|
+
for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) {
|
|
1885
|
+
const px = Math.floor(x) + dx;
|
|
1886
|
+
const py = Math.floor(y) + dy;
|
|
1887
|
+
const ddx = px + 0.5 - x;
|
|
1888
|
+
const ddy = py + 0.5 - y;
|
|
1889
|
+
const g = Math.exp(-(ddx * ddx + ddy * ddy) / (2 * wid * wid));
|
|
1890
|
+
const ix = (px % N + N) % N;
|
|
1891
|
+
const iy = (py % N + N) % N;
|
|
1892
|
+
fib[iy * N + ix] = fib[iy * N + ix] + tone * g * fade * 0.18;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) {
|
|
1897
|
+
const u = (i + 0.5) / N;
|
|
1898
|
+
const v = (j + 0.5) / N;
|
|
1899
|
+
const m = vnoise(u, v, 4, seed)[0] + 0.5 * vnoise(u, v, 8, seed + 1)[0];
|
|
1900
|
+
const t1 = vnoise(u, v, 32, seed + 2);
|
|
1901
|
+
const t2 = vnoise(u, v, 64, seed + 3);
|
|
1902
|
+
const gx = t1[1] + 0.5 * t2[1];
|
|
1903
|
+
const gy = t1[2] + 0.5 * t2[2];
|
|
1904
|
+
const o = (j * N + i) * 4;
|
|
1905
|
+
out[o] = Math.round(Math.min(Math.max(0.5 + m * 0.9, 0), 1) * 255);
|
|
1906
|
+
out[o + 1] = Math.round(Math.min(Math.max(0.5 + fib[j * N + i], 0), 1) * 255);
|
|
1907
|
+
out[o + 2] = Math.round(Math.min(Math.max(0.5 + gx / TOOTH_SCALE / 2, 0), 1) * 255);
|
|
1908
|
+
out[o + 3] = Math.round(Math.min(Math.max(0.5 + gy / TOOTH_SCALE / 2, 0), 1) * 255);
|
|
1909
|
+
}
|
|
1910
|
+
void fract;
|
|
1911
|
+
return out;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// ../desk/src/kit/physics.ts
|
|
1915
|
+
var SHADOW = { sigma: 2.2, alpha: 0.36, sigmaPerUnit: 0.9, alphaHeld: 0.4, slopeMax: 2.2 };
|
|
1916
|
+
var PHYSICS = {
|
|
1917
|
+
/** How far a HELD note rises (§7's lift, as a height), and the scale it reads at (§7's 1.05, kept). */
|
|
1918
|
+
lift: { height: 12, scale: 1.03 },
|
|
1919
|
+
/**
|
|
1920
|
+
* The contact shadow on the mat: Gaussian-blurred coverage of the sheet cast along the lamp's
|
|
1921
|
+
* ground direction by its height — σ and alpha at contact, σ growing per unit of height, the
|
|
1922
|
+
* alpha a held note casts, and the slope's cap (a note far from the lamp keeps a finite shadow).
|
|
1923
|
+
*/
|
|
1924
|
+
shadow: SHADOW,
|
|
1925
|
+
/**
|
|
1926
|
+
* A SLAB's shadow (a notebook, a whiteboard, a mini mat): the note's penumbra swept down its height, with a contact
|
|
1927
|
+
* term at its base (the tree-shadow design system's paper elevation, mat.css `--lift-1`) — theme.ts `BOOK.shadow` until K4a.
|
|
1928
|
+
*/
|
|
1929
|
+
slab: {
|
|
1930
|
+
penumbra: { sigma0: SHADOW.sigma, sigmaPerHeight: SHADOW.sigmaPerUnit, alpha: SHADOW.alpha },
|
|
1931
|
+
/** The contact term: σ, alpha, and the gap (world units) it has faded over once the object floats. */
|
|
1932
|
+
contact: { sigma: 1.1, alpha: 0.3, reach: 2.5 },
|
|
1933
|
+
slopeMax: SHADOW.slopeMax
|
|
1934
|
+
},
|
|
1935
|
+
/** The lamp's fill on a face turned away from it; how far past a flat face one turned toward it may brighten. */
|
|
1936
|
+
light: { ambient: 0.5, cap: 1.35 },
|
|
1937
|
+
/** The sole-selection ring (DESIGN.md §7), CSS px, in `--vf-select`. */
|
|
1938
|
+
ring: 1.5
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
// ../desk/src/kit/place.ts
|
|
1942
|
+
function rigidOf(p) {
|
|
1943
|
+
const cz = Math.cos(p.angle);
|
|
1944
|
+
const sz = Math.sin(p.angle);
|
|
1945
|
+
const cx = Math.cos(p.tiltX);
|
|
1946
|
+
const sx = Math.sin(p.tiltX);
|
|
1947
|
+
const cy = Math.cos(p.tiltY);
|
|
1948
|
+
const sy = Math.sin(p.tiltY);
|
|
1949
|
+
const rx = [1, 0, 0, 0, cx, -sx, 0, sx, cx];
|
|
1950
|
+
const ry = [cy, 0, sy, 0, 1, 0, -sy, 0, cy];
|
|
1951
|
+
const rz = [cz, -sz, 0, sz, cz, 0, 0, 0, 1];
|
|
1952
|
+
const mul2 = (a, b) => {
|
|
1953
|
+
const o = [];
|
|
1954
|
+
for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) o.push(a[i * 3] * b[j] + a[i * 3 + 1] * b[3 + j] + a[i * 3 + 2] * b[6 + j]);
|
|
1955
|
+
return o;
|
|
1956
|
+
};
|
|
1957
|
+
const r = mul2(rz, mul2(rx, ry));
|
|
1958
|
+
const t = [p.cx - r[2] * p.zc, p.cy - r[5] * p.zc, p.lift + p.zc - r[8] * p.zc];
|
|
1959
|
+
return { r, t };
|
|
1960
|
+
}
|
|
1961
|
+
function toWorld(g, x, y, z) {
|
|
1962
|
+
const r = g.r;
|
|
1963
|
+
return [r[0] * x + r[1] * y + r[2] * z + g.t[0], r[3] * x + r[4] * y + r[5] * z + g.t[1], r[6] * x + r[7] * y + r[8] * z + g.t[2]];
|
|
1964
|
+
}
|
|
1965
|
+
function dirToWorld(g, x, y, z) {
|
|
1966
|
+
const r = g.r;
|
|
1967
|
+
return [r[0] * x + r[1] * y + r[2] * z, r[3] * x + r[4] * y + r[5] * z, r[6] * x + r[7] * y + r[8] * z];
|
|
1968
|
+
}
|
|
1969
|
+
function toLocal(g, x, y, z) {
|
|
1970
|
+
const r = g.r;
|
|
1971
|
+
const dx = x - g.t[0];
|
|
1972
|
+
const dy = y - g.t[1];
|
|
1973
|
+
const dz = z - g.t[2];
|
|
1974
|
+
return [r[0] * dx + r[3] * dy + r[6] * dz, r[1] * dx + r[4] * dy + r[7] * dz, r[2] * dx + r[5] * dy + r[8] * dz];
|
|
1975
|
+
}
|
|
1976
|
+
function dirToLocal(g, x, y, z) {
|
|
1977
|
+
const r = g.r;
|
|
1978
|
+
return [r[0] * x + r[3] * y + r[6] * z, r[1] * x + r[4] * y + r[7] * z, r[2] * x + r[5] * y + r[8] * z];
|
|
1979
|
+
}
|
|
1980
|
+
function matrixOf(g) {
|
|
1981
|
+
const r = g.r;
|
|
1982
|
+
const m = new Float32Array(16);
|
|
1983
|
+
m[0] = r[0];
|
|
1984
|
+
m[1] = r[3];
|
|
1985
|
+
m[2] = r[6];
|
|
1986
|
+
m[4] = r[1];
|
|
1987
|
+
m[5] = r[4];
|
|
1988
|
+
m[6] = r[7];
|
|
1989
|
+
m[8] = r[2];
|
|
1990
|
+
m[9] = r[5];
|
|
1991
|
+
m[10] = r[8];
|
|
1992
|
+
m[12] = g.t[0];
|
|
1993
|
+
m[13] = g.t[1];
|
|
1994
|
+
m[14] = g.t[2];
|
|
1995
|
+
m[15] = 1;
|
|
1996
|
+
return m;
|
|
1997
|
+
}
|
|
1998
|
+
function lampDir(lamp, x, y, slopeMax) {
|
|
1999
|
+
let dx = lamp.x - x;
|
|
2000
|
+
let dy = lamp.y - y;
|
|
2001
|
+
const h = Math.max(lamp.h, 1e-6);
|
|
2002
|
+
const s = Math.hypot(dx, dy) / h;
|
|
2003
|
+
if (s > slopeMax) {
|
|
2004
|
+
dx *= slopeMax / s;
|
|
2005
|
+
dy *= slopeMax / s;
|
|
2006
|
+
}
|
|
2007
|
+
const l = Math.hypot(dx, dy, h);
|
|
2008
|
+
return [dx / l, dy / l, h / l];
|
|
2009
|
+
}
|
|
2010
|
+
function lightFrame(L, lo, hi, res, pad = 6, receivers = []) {
|
|
2011
|
+
const f = [-L[0], -L[1], -L[2]];
|
|
2012
|
+
let r = Math.abs(f[2]) > 0.999 ? [1, 0, 0] : [f[1] * 1 - f[2] * 0, f[2] * 0 - f[0] * 1, 0];
|
|
2013
|
+
const rl = Math.hypot(r[0], r[1], r[2]) || 1;
|
|
2014
|
+
r = [r[0] / rl, r[1] / rl, r[2] / rl];
|
|
2015
|
+
const u = [r[1] * f[2] - r[2] * f[1], r[2] * f[0] - r[0] * f[2], r[0] * f[1] - r[1] * f[0]];
|
|
2016
|
+
let x0 = Number.POSITIVE_INFINITY;
|
|
2017
|
+
let x1 = Number.NEGATIVE_INFINITY;
|
|
2018
|
+
let y0 = Number.POSITIVE_INFINITY;
|
|
2019
|
+
let y1 = Number.NEGATIVE_INFINITY;
|
|
2020
|
+
let d0 = Number.POSITIVE_INFINITY;
|
|
2021
|
+
let d1 = Number.NEGATIVE_INFINITY;
|
|
2022
|
+
for (let i = 0; i < 8; i++) {
|
|
2023
|
+
const P = [i & 1 ? hi[0] : lo[0], i & 2 ? hi[1] : lo[1], i & 4 ? hi[2] : lo[2]];
|
|
2024
|
+
const lx = P[0] * r[0] + P[1] * r[1] + P[2] * r[2];
|
|
2025
|
+
const ly = P[0] * u[0] + P[1] * u[1] + P[2] * u[2];
|
|
2026
|
+
const d = P[0] * f[0] + P[1] * f[1] + P[2] * f[2];
|
|
2027
|
+
x0 = Math.min(x0, lx);
|
|
2028
|
+
x1 = Math.max(x1, lx);
|
|
2029
|
+
y0 = Math.min(y0, ly);
|
|
2030
|
+
y1 = Math.max(y1, ly);
|
|
2031
|
+
d0 = Math.min(d0, d);
|
|
2032
|
+
d1 = Math.max(d1, d);
|
|
2033
|
+
}
|
|
2034
|
+
for (const P of receivers) d1 = Math.max(d1, P[0] * f[0] + P[1] * f[1] + P[2] * f[2]);
|
|
2035
|
+
const half = Math.max(x1 - x0, y1 - y0) / 2 + pad;
|
|
2036
|
+
const mx = (x0 + x1) / 2;
|
|
2037
|
+
const my = (y0 + y1) / 2;
|
|
2038
|
+
x0 = mx - half;
|
|
2039
|
+
x1 = mx + half;
|
|
2040
|
+
y0 = my - half;
|
|
2041
|
+
y1 = my + half;
|
|
2042
|
+
d0 -= pad;
|
|
2043
|
+
d1 += pad;
|
|
2044
|
+
const sx = 2 / (x1 - x0);
|
|
2045
|
+
const sy = 2 / (y1 - y0);
|
|
2046
|
+
const sd = 1 / (d1 - d0);
|
|
2047
|
+
const m = new Float32Array(16);
|
|
2048
|
+
m[0] = sx * r[0];
|
|
2049
|
+
m[4] = sx * r[1];
|
|
2050
|
+
m[8] = sx * r[2];
|
|
2051
|
+
m[12] = -sx * x0 - 1;
|
|
2052
|
+
m[1] = sy * u[0];
|
|
2053
|
+
m[5] = sy * u[1];
|
|
2054
|
+
m[9] = sy * u[2];
|
|
2055
|
+
m[13] = -sy * y0 - 1;
|
|
2056
|
+
m[2] = sd * f[0];
|
|
2057
|
+
m[6] = sd * f[1];
|
|
2058
|
+
m[10] = sd * f[2];
|
|
2059
|
+
m[14] = -sd * d0;
|
|
2060
|
+
m[15] = 1;
|
|
2061
|
+
return { matrix: m, texel: (x1 - x0) / res, depth: d1 - d0 };
|
|
2062
|
+
}
|
|
2063
|
+
function worldBounds(g, lo, hi) {
|
|
2064
|
+
const a = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY];
|
|
2065
|
+
const b = [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY];
|
|
2066
|
+
for (let i = 0; i < 8; i++) {
|
|
2067
|
+
const p = toWorld(g, i & 1 ? hi[0] : lo[0], i & 2 ? hi[1] : lo[1], i & 4 ? hi[2] : lo[2]);
|
|
2068
|
+
for (let k = 0; k < 3; k++) {
|
|
2069
|
+
a[k] = Math.min(a[k], p[k]);
|
|
2070
|
+
b[k] = Math.max(b[k], p[k]);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return { lo: a, hi: b };
|
|
2074
|
+
}
|
|
2075
|
+
function inverseOf(g) {
|
|
2076
|
+
const r = g.r;
|
|
2077
|
+
const rt = [r[0], r[3], r[6], r[1], r[4], r[7], r[2], r[5], r[8]];
|
|
2078
|
+
const t = g.t;
|
|
2079
|
+
return { r: rt, t: [-(rt[0] * t[0] + rt[1] * t[1] + rt[2] * t[2]), -(rt[3] * t[0] + rt[4] * t[1] + rt[5] * t[2]), -(rt[6] * t[0] + rt[7] * t[1] + rt[8] * t[2])] };
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
// ../desk/src/kit/print.ts
|
|
2083
|
+
var PRINT_RASTER = serviceKey("print");
|
|
2084
|
+
|
|
2085
|
+
// ../desk/src/kit/sdf.ts
|
|
2086
|
+
var sdRoundBox = (px, py, bx, by, r) => {
|
|
2087
|
+
const rr = Math.min(Math.max(r, 0), Math.min(bx, by));
|
|
2088
|
+
const qx = Math.abs(px) - bx + rr;
|
|
2089
|
+
const qy = Math.abs(py) - by + rr;
|
|
2090
|
+
return Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) + Math.min(Math.max(qx, qy), 0) - rr;
|
|
2091
|
+
};
|
|
2092
|
+
|
|
2093
|
+
// ../desk/src/kit/seeds.ts
|
|
2094
|
+
var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2095
|
+
var INDEX = Object.fromEntries([...ALPHABET].map((c, i) => [c, i]));
|
|
2096
|
+
function encodeSeeds(seeds) {
|
|
2097
|
+
const n = seeds.length;
|
|
2098
|
+
const bytes = new Uint8Array(n * 4);
|
|
2099
|
+
for (let i = 0; i < n; i++) {
|
|
2100
|
+
const v = seeds[i] >>> 0;
|
|
2101
|
+
bytes[4 * i] = v & 255;
|
|
2102
|
+
bytes[4 * i + 1] = v >>> 8 & 255;
|
|
2103
|
+
bytes[4 * i + 2] = v >>> 16 & 255;
|
|
2104
|
+
bytes[4 * i + 3] = v >>> 24 & 255;
|
|
2105
|
+
}
|
|
2106
|
+
let out = "";
|
|
2107
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
2108
|
+
const a = bytes[i];
|
|
2109
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
2110
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
2111
|
+
const t = a << 16 | b << 8 | c;
|
|
2112
|
+
out += ALPHABET[t >>> 18 & 63];
|
|
2113
|
+
out += ALPHABET[t >>> 12 & 63];
|
|
2114
|
+
if (i + 1 < bytes.length) out += ALPHABET[t >>> 6 & 63];
|
|
2115
|
+
if (i + 2 < bytes.length) out += ALPHABET[t & 63];
|
|
2116
|
+
}
|
|
2117
|
+
return out;
|
|
2118
|
+
}
|
|
2119
|
+
function decodeSeeds(s) {
|
|
2120
|
+
const bytes = [];
|
|
2121
|
+
let acc = 0;
|
|
2122
|
+
let bits = 0;
|
|
2123
|
+
for (const ch of s) {
|
|
2124
|
+
const v = INDEX[ch];
|
|
2125
|
+
if (v === void 0) break;
|
|
2126
|
+
acc = (acc << 6 | v) & 16777215;
|
|
2127
|
+
bits += 6;
|
|
2128
|
+
if (bits >= 8) {
|
|
2129
|
+
bits -= 8;
|
|
2130
|
+
bytes.push(acc >>> bits & 255);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
const out = [];
|
|
2134
|
+
for (let i = 0; i + 3 < bytes.length; i += 4) {
|
|
2135
|
+
out.push(bytes[i] | bytes[i + 1] << 8 | bytes[i + 2] << 16 | bytes[i + 3] << 24 | 0);
|
|
2136
|
+
}
|
|
2137
|
+
return out;
|
|
2138
|
+
}
|
|
2139
|
+
function seedsFor(text, stored, noteSeed) {
|
|
2140
|
+
const got = decodeSeeds(stored);
|
|
2141
|
+
const out = new Array(text.length);
|
|
2142
|
+
for (let i = 0; i < text.length; i++) out[i] = i < got.length ? got[i] : glyphSeed(noteSeed, i);
|
|
2143
|
+
return out;
|
|
2144
|
+
}
|
|
2145
|
+
var freshSeed = () => Math.random() * 2147483647 | 0;
|
|
2146
|
+
|
|
2147
|
+
// ../desk/src/kit/strokes.ts
|
|
2148
|
+
var BoardStroke = defineComponent("desk.stroke", {
|
|
2149
|
+
tool: field("string", { default: "marker" }),
|
|
2150
|
+
ink: field("string", { default: "black" }),
|
|
2151
|
+
tip: field("string", { default: "bullet" }),
|
|
2152
|
+
erase: field("bool", { default: false }),
|
|
2153
|
+
points: field("string", { default: "" }),
|
|
2154
|
+
speed: field("f64", { default: 400 }),
|
|
2155
|
+
times: field("string", { default: "" }),
|
|
2156
|
+
page: field("u32", { default: 0 }),
|
|
2157
|
+
seed: field("f64", { default: -1 })
|
|
2158
|
+
});
|
|
2159
|
+
var STROKE_TYPE = "desk.stroke";
|
|
2160
|
+
var StrokePrefab = definePrefab(STROKE_TYPE, {
|
|
2161
|
+
store: "durable",
|
|
2162
|
+
version: 4,
|
|
2163
|
+
components: [init(BoardStroke, { tool: "marker", ink: "black", tip: "bullet", erase: false, points: "", speed: 400, times: "", page: 0, seed: -1 })],
|
|
2164
|
+
relations: [ChildOf],
|
|
2165
|
+
// v4 (D7 #13): `seed` — the fibre seed the pen laid with; a v3 stroke keeps the seed its position gives it (−1), so nothing it drew changes
|
|
2166
|
+
migrate: { 1: (v) => ({ ...v, times: "" }), 2: (v) => ({ ...v, page: 0 }), 3: (v) => ({ ...v, seed: -1 }) }
|
|
2167
|
+
});
|
|
2168
|
+
var seedOfRow = (s, n) => s.seed !== null && s.seed !== void 0 && s.seed >= 0 ? s.seed : strokeSeed(n);
|
|
2169
|
+
function encodePoints(points) {
|
|
2170
|
+
const f = new Float32Array(points.length * 2);
|
|
2171
|
+
points.forEach(([x, y], i) => {
|
|
2172
|
+
f[2 * i] = x;
|
|
2173
|
+
f[2 * i + 1] = y;
|
|
2174
|
+
});
|
|
2175
|
+
return encodeSeeds(new Uint32Array(f.buffer));
|
|
2176
|
+
}
|
|
2177
|
+
function decodePoints(s) {
|
|
2178
|
+
const words = decodeSeeds(s);
|
|
2179
|
+
const f = new Float32Array(new Int32Array(words).buffer);
|
|
2180
|
+
const out = [];
|
|
2181
|
+
for (let i = 0; i + 1 < f.length; i += 2) out.push([f[i], f[i + 1]]);
|
|
2182
|
+
return out;
|
|
2183
|
+
}
|
|
2184
|
+
function encodeTimes(times) {
|
|
2185
|
+
return encodeSeeds(new Uint32Array(Float32Array.from(times).buffer));
|
|
2186
|
+
}
|
|
2187
|
+
function decodeTimes(s) {
|
|
2188
|
+
return Array.from(new Float32Array(new Int32Array(decodeSeeds(s)).buffer));
|
|
2189
|
+
}
|
|
2190
|
+
function meanSpeed(points, times) {
|
|
2191
|
+
let length = 0;
|
|
2192
|
+
for (let i = 1; i < points.length; i++) {
|
|
2193
|
+
const a = points[i - 1];
|
|
2194
|
+
const b = points[i];
|
|
2195
|
+
length += Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
2196
|
+
}
|
|
2197
|
+
const ms = (times[times.length - 1] ?? 0) - (times[0] ?? 0);
|
|
2198
|
+
return length > 0 && ms > 0 ? length / ms * 1e3 : 400;
|
|
2199
|
+
}
|
|
2200
|
+
function strokeRow(s) {
|
|
2201
|
+
const points = s.points ?? [];
|
|
2202
|
+
const timed = s.times !== void 0 && s.times.length === points.length && points.length > 0;
|
|
2203
|
+
return {
|
|
2204
|
+
tool: s.tool ?? "marker",
|
|
2205
|
+
ink: s.ink ?? "black",
|
|
2206
|
+
tip: s.tip ?? "bullet",
|
|
2207
|
+
erase: s.erase === true,
|
|
2208
|
+
points: encodePoints(points),
|
|
2209
|
+
speed: s.speed ?? (timed ? meanSpeed(points, s.times ?? []) : 400),
|
|
2210
|
+
times: timed ? encodeTimes(s.times ?? []) : "",
|
|
2211
|
+
page: Math.max(0, Math.floor(s.page ?? 0)),
|
|
2212
|
+
seed: s.seed !== void 0 && Number.isFinite(s.seed) && s.seed >= 0 ? s.seed : -1
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function addStroke(tx, board, s) {
|
|
2216
|
+
const e = tx.spawnPrefab(StrokePrefab, [init(BoardStroke, strokeRow(s))]);
|
|
2217
|
+
tx.setRelation(e, ChildOf, board);
|
|
2218
|
+
return e;
|
|
2219
|
+
}
|
|
2220
|
+
var strokeSeed = (n) => n * 97.13 % 1e3;
|
|
2221
|
+
var heldPressesQ = defineQuery([Pointer, LocalPointer, HeldPress]);
|
|
2222
|
+
function inking(world) {
|
|
2223
|
+
let yes = false;
|
|
2224
|
+
world.query(heldPressesQ).each((b) => {
|
|
2225
|
+
for (const r of b) if (world.read(b.entity(r), HeldPress).kind === "tool") yes = true;
|
|
2226
|
+
});
|
|
2227
|
+
return yes;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// ../desk/src/kit/uniform.ts
|
|
2231
|
+
var sentBytes = (size) => new Uint8Array(size);
|
|
2232
|
+
function writeChanged(queue, buffer, block, sent, elements) {
|
|
2233
|
+
const bytes = block.view(elements);
|
|
2234
|
+
if (bytes.byteLength > sent.byteLength) throw new Error(`writeChanged: ${bytes.byteLength} bytes into a ${sent.byteLength}-byte record of what was sent`);
|
|
2235
|
+
let same = true;
|
|
2236
|
+
for (let i = 0; i < bytes.byteLength; i++) if (bytes[i] !== sent[i]) {
|
|
2237
|
+
same = false;
|
|
2238
|
+
break;
|
|
2239
|
+
}
|
|
2240
|
+
if (same) return false;
|
|
2241
|
+
queue.writeBuffer(buffer, 0, bytes);
|
|
2242
|
+
sent.set(bytes);
|
|
2243
|
+
return true;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// ../desk/src/lattice/line.ts
|
|
2247
|
+
var clamp013 = (x) => Math.min(Math.max(x, 0), 1);
|
|
2248
|
+
function lineWeight(cellPx, fadeIn, law) {
|
|
2249
|
+
const s = smoothstep(fadeIn[0], fadeIn[1], cellPx);
|
|
2250
|
+
const t = clamp013(Math.log10(Math.max(cellPx, 1e-9) / Math.max(fadeIn[1], 1e-9)));
|
|
2251
|
+
return {
|
|
2252
|
+
halfWidth: law.thin + (law.thick - law.thin) * t,
|
|
2253
|
+
alpha: law.alphaThin * s + (law.alphaThick - law.alphaThin) * t
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
export {
|
|
2258
|
+
ZOOM_MIN,
|
|
2259
|
+
ZOOM_MAX,
|
|
2260
|
+
boxOf,
|
|
2261
|
+
smoothstep,
|
|
2262
|
+
lod,
|
|
2263
|
+
DAY_LIGHT,
|
|
2264
|
+
linear,
|
|
2265
|
+
dayLuminance,
|
|
2266
|
+
nightLight,
|
|
2267
|
+
lightValues,
|
|
2268
|
+
cssColor,
|
|
2269
|
+
rgb,
|
|
2270
|
+
GRID,
|
|
2271
|
+
MAT,
|
|
2272
|
+
MAT_COLORS,
|
|
2273
|
+
MAT_GRID,
|
|
2274
|
+
NIGHT,
|
|
2275
|
+
EIGENGRAU,
|
|
2276
|
+
MAT_LIGHT,
|
|
2277
|
+
PORTAL,
|
|
2278
|
+
MARKS,
|
|
2279
|
+
TRAY,
|
|
2280
|
+
themeFrom,
|
|
2281
|
+
PORTAL_CHAIN,
|
|
2282
|
+
chainOf,
|
|
2283
|
+
clipOf,
|
|
2284
|
+
boxOfPortal,
|
|
2285
|
+
intersectBox,
|
|
2286
|
+
PORTAL_GATE,
|
|
2287
|
+
PORTAL_CAP,
|
|
2288
|
+
portalPresence,
|
|
2289
|
+
faceCovers,
|
|
2290
|
+
scissorOf,
|
|
2291
|
+
tilted,
|
|
2292
|
+
MatUniforms,
|
|
2293
|
+
GLYPH_PAD,
|
|
2294
|
+
GLYPHS,
|
|
2295
|
+
RULER_GLYPHS,
|
|
2296
|
+
NO_GLYPHS,
|
|
2297
|
+
DEFAULT_MAT_CONFIG,
|
|
2298
|
+
HERO_MATRIX,
|
|
2299
|
+
STILL_MAT_FRAME,
|
|
2300
|
+
NOISE_SIZE,
|
|
2301
|
+
PLATE_SIZE,
|
|
2302
|
+
matUniformValues,
|
|
2303
|
+
litByOwn,
|
|
2304
|
+
DEFAULT_GRID,
|
|
2305
|
+
dressScale,
|
|
2306
|
+
dressGrid,
|
|
2307
|
+
gridStats,
|
|
2308
|
+
easeLift,
|
|
2309
|
+
easeIsland,
|
|
2310
|
+
NbUniforms,
|
|
2311
|
+
NbBook,
|
|
2312
|
+
shaderText,
|
|
2313
|
+
KIT_WGSL_FILES,
|
|
2314
|
+
kitWgsl,
|
|
2315
|
+
LAYER_COMPOSITE_FILE,
|
|
2316
|
+
layerComposite,
|
|
2317
|
+
layerCompositeLayout,
|
|
2318
|
+
attachmentOf,
|
|
2319
|
+
clip,
|
|
2320
|
+
LayeredKind,
|
|
2321
|
+
BOX_STEP,
|
|
2322
|
+
BoxTargets,
|
|
2323
|
+
LAYER_IDLE_MS,
|
|
2324
|
+
spring,
|
|
2325
|
+
settled,
|
|
2326
|
+
SPRINGS,
|
|
2327
|
+
visibleRect,
|
|
2328
|
+
portalAffine,
|
|
2329
|
+
solveFlightStart,
|
|
2330
|
+
outgoingCamera,
|
|
2331
|
+
flightCamera,
|
|
2332
|
+
FIT,
|
|
2333
|
+
flightOpacity,
|
|
2334
|
+
boundsOf,
|
|
2335
|
+
arrivalCamera,
|
|
2336
|
+
HOLD,
|
|
2337
|
+
isNarrow,
|
|
2338
|
+
readingTarget,
|
|
2339
|
+
homePose,
|
|
2340
|
+
HELD_USER_REST,
|
|
2341
|
+
heldPose,
|
|
2342
|
+
carryOf,
|
|
2343
|
+
progressOf,
|
|
2344
|
+
heldFocus,
|
|
2345
|
+
heldCamera,
|
|
2346
|
+
heldFrame,
|
|
2347
|
+
focusOf,
|
|
2348
|
+
PAPER_FINISH,
|
|
2349
|
+
VINYL_FINISH,
|
|
2350
|
+
finishOf,
|
|
2351
|
+
insideViewOfFace,
|
|
2352
|
+
insidePresent,
|
|
2353
|
+
flightPresent,
|
|
2354
|
+
HANDOVER,
|
|
2355
|
+
flightLights,
|
|
2356
|
+
lampOf,
|
|
2357
|
+
serviceKey,
|
|
2358
|
+
service,
|
|
2359
|
+
createServices,
|
|
2360
|
+
BLOB_STORE,
|
|
2361
|
+
PICTURE_DECODER,
|
|
2362
|
+
RGBA_TYPE,
|
|
2363
|
+
hashBytes,
|
|
2364
|
+
createMemoryBlobStore,
|
|
2365
|
+
TEXT_RASTER,
|
|
2366
|
+
hashHand,
|
|
2367
|
+
glyphSeed,
|
|
2368
|
+
layoutText,
|
|
2369
|
+
caretAt,
|
|
2370
|
+
glyphBox,
|
|
2371
|
+
carrySeeds,
|
|
2372
|
+
HAND,
|
|
2373
|
+
PENS,
|
|
2374
|
+
PEN_FACES,
|
|
2375
|
+
tailBase,
|
|
2376
|
+
chainBytes,
|
|
2377
|
+
Layers,
|
|
2378
|
+
plainFormat,
|
|
2379
|
+
LayerArray,
|
|
2380
|
+
eyeOf,
|
|
2381
|
+
project,
|
|
2382
|
+
deskAt,
|
|
2383
|
+
unproject,
|
|
2384
|
+
rayThrough,
|
|
2385
|
+
eyeValues,
|
|
2386
|
+
VERTEX_FLOATS,
|
|
2387
|
+
VERTEX_BYTES,
|
|
2388
|
+
FLAG_TWO_SIDED,
|
|
2389
|
+
MeshWriter,
|
|
2390
|
+
mipCount,
|
|
2391
|
+
generateMips,
|
|
2392
|
+
PAPER_TEX,
|
|
2393
|
+
TOOTH_SCALE,
|
|
2394
|
+
paperTexture,
|
|
2395
|
+
PHYSICS,
|
|
2396
|
+
rigidOf,
|
|
2397
|
+
toWorld,
|
|
2398
|
+
dirToWorld,
|
|
2399
|
+
toLocal,
|
|
2400
|
+
dirToLocal,
|
|
2401
|
+
matrixOf,
|
|
2402
|
+
lampDir,
|
|
2403
|
+
lightFrame,
|
|
2404
|
+
worldBounds,
|
|
2405
|
+
inverseOf,
|
|
2406
|
+
PRINT_RASTER,
|
|
2407
|
+
sdRoundBox,
|
|
2408
|
+
encodeSeeds,
|
|
2409
|
+
decodeSeeds,
|
|
2410
|
+
seedsFor,
|
|
2411
|
+
freshSeed,
|
|
2412
|
+
BoardStroke,
|
|
2413
|
+
STROKE_TYPE,
|
|
2414
|
+
StrokePrefab,
|
|
2415
|
+
seedOfRow,
|
|
2416
|
+
encodePoints,
|
|
2417
|
+
decodePoints,
|
|
2418
|
+
encodeTimes,
|
|
2419
|
+
decodeTimes,
|
|
2420
|
+
meanSpeed,
|
|
2421
|
+
strokeRow,
|
|
2422
|
+
addStroke,
|
|
2423
|
+
strokeSeed,
|
|
2424
|
+
inking,
|
|
2425
|
+
sentBytes,
|
|
2426
|
+
writeChanged,
|
|
2427
|
+
lineWeight
|
|
2428
|
+
};
|
|
2429
|
+
//# sourceMappingURL=chunk-BA7XJC27.js.map
|