@vibecook/ice 0.14.0 → 0.15.0-desk.2
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/dist/{chunk-ZPUPBULB.js → chunk-52GMQ2BN.js} +1320 -450
- package/dist/chunk-52GMQ2BN.js.map +1 -0
- package/dist/{chunk-BA7XJC27.js → chunk-K4HUZAYJ.js} +49 -34
- package/dist/chunk-K4HUZAYJ.js.map +1 -0
- package/dist/{chunk-DYK6KNLG.js → chunk-PFJN3XN2.js} +39 -14
- package/dist/chunk-PFJN3XN2.js.map +1 -0
- package/dist/{chunk-IBWWLA6D.js → chunk-U4YMA4TJ.js} +6 -5
- package/dist/chunk-U4YMA4TJ.js.map +1 -0
- package/dist/desk-kit.js +2 -2
- package/dist/desk-objects.js +15 -9
- package/dist/desk-objects.js.map +1 -1
- package/dist/desk.js +31 -3
- package/dist/devtools.js +1 -1
- package/dist/dom.js +2 -2
- package/dist/index.js +1 -1
- package/dist/react.js +9 -7
- package/dist/react.js.map +1 -1
- package/dist/types/packages/core/src/index.d.ts +1 -1
- package/dist/types/packages/core/src/interaction/install.d.ts +7 -1
- package/dist/types/packages/core/src/systems/held.d.ts +3 -1
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +16 -0
- package/dist/types/packages/core/src/systems/l2-recognize.d.ts +3 -1
- package/dist/types/packages/core/src/systems/press-button.d.ts +25 -0
- package/dist/types/packages/core/src/systems/tray.d.ts +3 -0
- package/dist/types/packages/desk/src/compose/builder.d.ts +22 -1
- package/dist/types/packages/desk/src/compose/marks.d.ts +7 -0
- package/dist/types/packages/desk/src/compose/pick.d.ts +1 -1
- package/dist/types/packages/desk/src/compose/reflector.d.ts +9 -0
- package/dist/types/packages/desk/src/engine/budget.d.ts +5 -0
- package/dist/types/packages/desk/src/faults.d.ts +38 -0
- package/dist/types/packages/desk/src/ground.d.ts +158 -9
- package/dist/types/packages/desk/src/hold/focus.d.ts +1 -1
- package/dist/types/packages/desk/src/hold/pose.d.ts +27 -7
- package/dist/types/packages/desk/src/host/index.d.ts +1 -1
- package/dist/types/packages/desk/src/host/layer.d.ts +110 -4
- package/dist/types/packages/desk/src/index.d.ts +5 -2
- package/dist/types/packages/desk/src/kind.d.ts +7 -5
- package/dist/types/packages/desk/src/kinds/world.d.ts +20 -2
- package/dist/types/packages/desk/src/kit/editor.d.ts +3 -1
- package/dist/types/packages/desk/src/kit/hold.d.ts +1 -1
- package/dist/types/packages/desk/src/missing/layout.d.ts +27 -0
- package/dist/types/packages/desk/src/missing/object.d.ts +6 -0
- package/dist/types/packages/desk/src/missing/pass.d.ts +39 -0
- package/dist/types/packages/desk/src/missing/shaders.d.ts +7 -0
- package/dist/types/packages/desk/src/missing/slots.d.ts +8 -0
- package/dist/types/packages/desk/src/shaders.gen.d.ts +5 -4
- package/dist/types/packages/desk/src/theme.d.ts +22 -0
- package/dist/types/packages/desk/src/tray/drawer.d.ts +16 -5
- package/dist/types/packages/desk/src/tray/flux.d.ts +4 -1
- package/dist/types/packages/desk/src/tray/index.d.ts +1 -1
- package/dist/types/packages/desk/src/tray/pass.d.ts +7 -2
- package/dist/types/packages/desk/src/tray/specimens.d.ts +21 -6
- package/dist/types/packages/dom/src/desk-host.d.ts +15 -2
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +6 -0
- package/dist/types/packages/objects/src/board/layout.d.ts +1 -1
- package/dist/types/packages/objects/src/shaders.gen.d.ts +1 -1
- package/dist/types/packages/react/src/selection-menu.d.ts +5 -2
- package/package.json +1 -1
- package/dist/chunk-BA7XJC27.js.map +0 -1
- package/dist/chunk-DYK6KNLG.js.map +0 -1
- package/dist/chunk-IBWWLA6D.js.map +0 -1
- package/dist/chunk-ZPUPBULB.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RGB } from "../../../desk/src/index";
|
|
2
2
|
import type { BoardGeometry, WorldBox } from "./board";
|
|
3
|
-
export declare const Board: import("../../../desk/src/engine/index").StructDef<"ring" | "lamp" | "tier" | "tool" | "pen" | "frame" | "surface" | "inner" | "centre" | "half" | "quad" | "lift" | "alpha" | "slope" | "radius" | "thick" | "eraser" | "sheen" | "texels" | "innerR" | "recess" | "density" | "wet" | "laying" | "
|
|
3
|
+
export declare const Board: import("../../../desk/src/engine/index").StructDef<"ring" | "lamp" | "tier" | "tool" | "pen" | "frame" | "surface" | "inner" | "centre" | "half" | "quad" | "lift" | "scale" | "alpha" | "slope" | "radius" | "thick" | "eraser" | "sheen" | "texels" | "innerR" | "recess" | "density" | "wet" | "laying" | "metal" | "penPose" | "penInk">;
|
|
4
4
|
export declare const BoardUniforms: import("../../../desk/src/engine/index").StructDef<"light" | "shadow" | "shadow2" | "select" | "pen" | "felt" | "block" | "castTint" | "sheen" | "mel" | "lip" | "glint" | "barrel" | "wood" | "pen2" | "block2">;
|
|
5
5
|
/** One footprint of the pen (stroke.ts `STAMP_FLOATS` — the same eight floats, in the same order). */
|
|
6
6
|
export declare const Stamp: import("../../../desk/src/engine/index").StructDef<"size" | "s" | "seed" | "dir" | "pos" | "flow">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const WGSL: {
|
|
2
2
|
readonly "board/board-card.wgsl": "// The whiteboard as the board pass AND the flat-card pipeline draw it (K7b, design-016 §6 K7): the corner's clip position and\n// the THUMBNAIL's ink, over the board's bindings by their own names — `boards`, `board_k`, `board_thumbs` … — beside the slot's\n// `u`, `gobo_tex`, `noise_tex`, which whichever entry composes them declares. (Its fragment is the pass entry's `fs`, written\n// out — the card splices the same statements, shaders.ts `BOARD_FRAGMENT`.) `shade_board` reads its ink\n// through `board_ink` and the live board's layers through `board_stroke` / `board_wet`: the pass's entry reads the pool and\n// the live layers there, else the thumbnail; the card binds neither and reads the thumbnail alone (a board with a raster, or\n// being written on, is the pass's to draw).\n\n// The thumbnails' layer side (board-pass.ts `BOARD_THUMB`).\nconst BOARD_THUMB = 512.0;\n\nconst BD_CORNERS = array<vec2f, 6>(\n vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),\n vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0),\n);\n\n// A board's ink at (uv, lod) from its THUMBNAIL — lod in its raster's chain: the layer (`tier.x`), which starts at the level k of\n// its source's chain (`tier.y`) — the chain's tail at the layer's origin, its edge carried to the layer's — at lod − k.\nfn board_thumb(B: Board, uv: vec2f, lod: f32) -> vec4f {\n let k = B.tier.y;\n let size = max(floor(B.texels / exp2(k)), vec2f(1.0));\n return textureSampleLevel(board_thumbs, board_ink_samp, uv * size / BOARD_THUMB, i32(B.tier.x), max(lod - k, 0.0));\n}\n\nfn board_quad(slot: u32, vid: u32) -> vec4f {\n let B = boards[slot];\n let zoom = max(u.cam.z, 1.0e-12);\n let lo = (B.quad.xy - u.cam.xy) * zoom; // CSS px\n let hi = (B.quad.zw - u.cam.xy) * zoom;\n if (hi.x < u.box.x || hi.y < u.box.y || lo.x > u.box.x + u.box.z || lo.y > u.box.y + u.box.w) { return vec4f(2.0, 2.0, 2.0, 1.0); } // degenerate: a culled board collapses\n let corner = BD_CORNERS[vid];\n let pos = mix(lo, hi, corner * 0.5 + 0.5);\n return vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n}\n";
|
|
3
|
-
readonly "board/board-pass.wgsl": "// The board pass — the whiteboards: one quad per board (its extent the record's own: the slab,\n// its shadow, the tool in the hand — resolved on the CPU), one `shade_board` per covered\n// fragment, premultiplied output over the mat. Group 0 is the slot's (the mat's block, the\n// knobs, the records, the gobo, the noise, the samplers); group 1 is the SAME for every board\n// (K6a, K-L4 — a run of boards is one instanced draw): the far-LOD thumbnails' array, the pool's\n// rasters' ink (with mips), the live board's stroke and wet layers; a board's record says where\n// its ink is (`tier`). The camera, the box and the portal chain come through the mat's block, as\n// for the note and the notebook. The quad, the fragment and the thumbnail's ink are\n// board-card.wgsl's — the flat-card pipeline draws a board with the same three (K7b).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> board_k: BoardUniforms;\n@group(0) @binding(2) var<storage, read> boards: array<Board>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var board_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var board_noise_samp: sampler;\n@group(0) @binding(7) var board_ink_samp: sampler; // trilinear — the ink seen small reads its mips\n@group(0) @binding(8) var board_lin_samp: sampler; // bilinear, level 0 — the stroke and the wet layer\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index → the record's slot (persistent records, D6)\n@group(1) @binding(0) var board_thumbs: texture_2d_array<f32>; // every board's ink chain from its first level ≤ BOARD_THUMB², a layer each\n@group(1) @binding(1) var ink0: texture_2d<f32>; // the pool: a board's raster — premultiplied linear ink, coverage in alpha\n@group(1) @binding(2) var ink1: texture_2d<f32>;\n@group(1) @binding(3) var ink2: texture_2d<f32>;\n@group(1) @binding(4) var ink3: texture_2d<f32>;\n@group(1) @binding(5) var ink4: texture_2d<f32>;\n@group(1) @binding(6) var ink5: texture_2d<f32>;\n@group(1) @binding(7) var ink6: texture_2d<f32>;\n@group(1) @binding(8) var ink7: texture_2d<f32>;\n@group(1) @binding(9) var stroke_live: texture_2d<f32>; // the live board's stroke being laid (r8)\n@group(1) @binding(10) var wet_live: texture_2d<f32>; // …and how wet its ink still is (r8)\n\n// A board's ink at (uv, lod) — lod in its raster's chain. `tier`: its thumbnail's layer, the level of its source's chain the\n// layer starts at (k), its pool slot (−1: none), live. The slot's raster, else the layer (board-card.wgsl `board_thumb`).\nfn board_ink(B: Board, uv: vec2f, lod: f32) -> vec4f {\n switch i32(B.tier.z) {\n case 0: { return textureSampleLevel(ink0, board_ink_samp, uv, lod); }\n case 1: { return textureSampleLevel(ink1, board_ink_samp, uv, lod); }\n case 2: { return textureSampleLevel(ink2, board_ink_samp, uv, lod); }\n case 3: { return textureSampleLevel(ink3, board_ink_samp, uv, lod); }\n case 4: { return textureSampleLevel(ink4, board_ink_samp, uv, lod); }\n case 5: { return textureSampleLevel(ink5, board_ink_samp, uv, lod); }\n case 6: { return textureSampleLevel(ink6, board_ink_samp, uv, lod); }\n case 7: { return textureSampleLevel(ink7, board_ink_samp, uv, lod); }\n default: {}\n }\n return board_thumb(B, uv, lod);\n}\n\n// The live board's stroke being laid, and how wet its ink is — 0 on any other board (their layers are not bound).\nfn board_stroke(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(stroke_live, board_lin_samp, uv, 0.0).r;\n}\nfn board_wet(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(wet_live, board_lin_samp, uv, 0.0).r;\n}\n\nstruct BoardOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> BoardOut {\n var out: BoardOut;\n out.idx = order[iid];\n out.clip = board_quad(out.idx, vid);\n return out;\n}\n\n// The fragment, written out: shade_board reached through one more function compiles to one LSB off on a pixel or two a scene\n// (K7b, measured) — so the flat-card pipeline splices THESE statements (shaders.ts `BOARD_FRAGMENT`, a test holds them equal).\n@fragment\nfn fs(in: BoardOut) -> @location(0) vec4f {\n let B = boards[in.idx];\n let zoom = max(u.cam.z, 1.0e-12);\n let dpr = max(u.cam.w, 1.
|
|
3
|
+
readonly "board/board-pass.wgsl": "// The board pass — the whiteboards: one quad per board (its extent the record's own: the slab,\n// its shadow, the tool in the hand — resolved on the CPU), one `shade_board` per covered\n// fragment, premultiplied output over the mat. Group 0 is the slot's (the mat's block, the\n// knobs, the records, the gobo, the noise, the samplers); group 1 is the SAME for every board\n// (K6a, K-L4 — a run of boards is one instanced draw): the far-LOD thumbnails' array, the pool's\n// rasters' ink (with mips), the live board's stroke and wet layers; a board's record says where\n// its ink is (`tier`). The camera, the box and the portal chain come through the mat's block, as\n// for the note and the notebook. The quad, the fragment and the thumbnail's ink are\n// board-card.wgsl's — the flat-card pipeline draws a board with the same three (K7b).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> board_k: BoardUniforms;\n@group(0) @binding(2) var<storage, read> boards: array<Board>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var board_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var board_noise_samp: sampler;\n@group(0) @binding(7) var board_ink_samp: sampler; // trilinear — the ink seen small reads its mips\n@group(0) @binding(8) var board_lin_samp: sampler; // bilinear, level 0 — the stroke and the wet layer\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index → the record's slot (persistent records, D6)\n@group(1) @binding(0) var board_thumbs: texture_2d_array<f32>; // every board's ink chain from its first level ≤ BOARD_THUMB², a layer each\n@group(1) @binding(1) var ink0: texture_2d<f32>; // the pool: a board's raster — premultiplied linear ink, coverage in alpha\n@group(1) @binding(2) var ink1: texture_2d<f32>;\n@group(1) @binding(3) var ink2: texture_2d<f32>;\n@group(1) @binding(4) var ink3: texture_2d<f32>;\n@group(1) @binding(5) var ink4: texture_2d<f32>;\n@group(1) @binding(6) var ink5: texture_2d<f32>;\n@group(1) @binding(7) var ink6: texture_2d<f32>;\n@group(1) @binding(8) var ink7: texture_2d<f32>;\n@group(1) @binding(9) var stroke_live: texture_2d<f32>; // the live board's stroke being laid (r8)\n@group(1) @binding(10) var wet_live: texture_2d<f32>; // …and how wet its ink still is (r8)\n\n// A board's ink at (uv, lod) — lod in its raster's chain. `tier`: its thumbnail's layer, the level of its source's chain the\n// layer starts at (k), its pool slot (−1: none), live. The slot's raster, else the layer (board-card.wgsl `board_thumb`).\nfn board_ink(B: Board, uv: vec2f, lod: f32) -> vec4f {\n switch i32(B.tier.z) {\n case 0: { return textureSampleLevel(ink0, board_ink_samp, uv, lod); }\n case 1: { return textureSampleLevel(ink1, board_ink_samp, uv, lod); }\n case 2: { return textureSampleLevel(ink2, board_ink_samp, uv, lod); }\n case 3: { return textureSampleLevel(ink3, board_ink_samp, uv, lod); }\n case 4: { return textureSampleLevel(ink4, board_ink_samp, uv, lod); }\n case 5: { return textureSampleLevel(ink5, board_ink_samp, uv, lod); }\n case 6: { return textureSampleLevel(ink6, board_ink_samp, uv, lod); }\n case 7: { return textureSampleLevel(ink7, board_ink_samp, uv, lod); }\n default: {}\n }\n return board_thumb(B, uv, lod);\n}\n\n// The live board's stroke being laid, and how wet its ink is — 0 on any other board (their layers are not bound).\nfn board_stroke(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(stroke_live, board_lin_samp, uv, 0.0).r;\n}\nfn board_wet(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(wet_live, board_lin_samp, uv, 0.0).r;\n}\n\nstruct BoardOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> BoardOut {\n var out: BoardOut;\n out.idx = order[iid];\n out.clip = board_quad(out.idx, vid);\n return out;\n}\n\n// The fragment, written out: shade_board reached through one more function compiles to one LSB off on a pixel or two a scene\n// (K7b, measured) — so the flat-card pipeline splices THESE statements (shaders.ts `BOARD_FRAGMENT`, a test holds them equal).\n@fragment\nfn fs(in: BoardOut) -> @location(0) vec4f {\n let B = boards[in.idx];\n let zoom = max(u.cam.z, 1.0e-12);\n let dpr = max(u.cam.w, 1.0e-6);\n let px = 1.0 / (zoom * dpr); // world per device px\n let css = 1.0 / zoom; // world per CSS px\n let p = in.clip.xy * px + u.cam.xy; // device px → world\n let c = shade_board(B, u, board_k, p, px, css, in.clip.xy, gobo_tex, board_gobo_samp, noise_tex, board_noise_samp);\n if (c.a < 0.002) { discard; }\n // Premultiplied: the presentation's opacity through the portal clip scales every channel.\n return c * (u.view.w * portal_cover(in.clip.xy / dpr, u.portals, u.clips, dpr));\n}\n";
|
|
4
4
|
readonly "board/board.wgsl": "// THE WHITEBOARD (BOARD.md) — a desk dry-erase board under the mat's one lamp. A PURE module:\n// the record `Board`, the pass's knobs `BoardUniforms`, the mat's block `MatUniforms` and the\n// textures arrive as parameters; it names no colour.\n//\n// The board is a slab on the mat: an aluminium frame round a melamine face recessed a hair\n// below it. The camera is top-down, so the depth is the lamp's work, as for the note and the\n// notebook: the slab casts its footprint along the lamp's ground slope from every height it\n// spans, each blurred by its own height, with a contact shadow at its base; the frame's\n// profile rolls at both rims — turned toward the lamp it brightens and glints, turned away it\n// falls into shade; the frame's lip shades the melamine on the lamp's side. The melamine\n// carries the ink raster — a filter laid into its albedo before the light, so the dapple\n// crosses the drawing as it crosses the mat — its waviness, the ghost of old erasures, fresh\n// ink's wet gloss, and a SHEEN: the window the lamp shines through, mirrored in the glossy face\n// and seen by an eye over the board, so it lands on one part of it and slides as the pointer\n// moves (the research whiteboard's room, reduced to the one bright thing a desk has over it).\n// On the board lies a capped marker; while it is open, the tool in the hand is drawn at the\n// pointer — its shadow meets its tip as it presses. The colour chain is the note's: the\n// reference's grade on the sRGB byte by day, the eye's chain on its linear value by night.\n\nfn bd_cov(d: f32, px: f32) -> f32 { return clamp(0.5 - d / px, 0.0, 1.0); }\nfn bd_line(d: f32, w: f32, px: f32) -> f32 { return bd_cov(d, px) - bd_cov(d + w, px); }\nfn bd_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-a * a);\n return s * y;\n}\n// Gaussian-blurred coverage — a shadow, straight out of the field.\nfn bd_blur(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * bd_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n// Premultiplied \"src over dst\".\nfn bd_over(src: vec4f, dst: vec4f) -> vec4f { return src + dst * (1.0 - src.a); }\n// A world point raised `h` world units off the desk, in the projector's frame (the note's `paper_desk_at`).\nfn bd_desk_at(u: MatUniforms, world: vec2f, h: f32) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w + h * u.plane.z, u.plane.y + world.y * u.plane.z);\n}\nfn bd_encode(c: vec3f) -> vec3f {\n let x = clamp(c, vec3f(0.0), vec3f(1.0));\n return mix(x * 12.92, 1.055 * pow(x, vec3f(1.0 / 2.4)) - 0.055, step(vec3f(0.0031308), x));\n}\n\n// The chain, as the note's `paper_colour`: by day the reference's grade on the sRGB albedo — lit,\n// the identity, so a configured byte is the drawn byte — or, at `chain` 1, through the mat's own\n// double gamma; by night the eye's chain on the linear albedo, as the mat's; between, the cross-fade.\nfn bd_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32, chain: f32) -> vec3f {\n let day = shade_mat(u, mix(albedo, srgb_to_linear(albedo), chain), 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// The lamp on a face (the notebook's `bk_lambert`): its fill, plus the lambert normalised so a\n// flat face reads 1 and capped so a face turned toward a low lamp brightens only so far.\nfn bd_lambert(n: vec3f, L: vec3f, k: BoardUniforms) -> f32 {\n let d = max(dot(n, L), 0.0) / max(L.z, 0.05);\n return k.light.x + (1.0 - k.light.x) * min(d, k.light.y);\n}\n\n// A glint: the lamp mirrored by a face toward the eye straight above (Blinn's half vector).\nfn bd_glint(n: vec3f, L: vec3f, power: f32) -> f32 {\n return pow(max(dot(n, normalize(L + vec3f(0.0, 0.0, 1.0))), 0.0), power);\n}\n\n// How bright a glint can be under the light in force: the Sun's, or the Moon's much less.\nfn bd_lit(u: MatUniforms, k: BoardUniforms) -> f32 { return mix(1.0, k.light.w, clamp(u.night.x, 0.0, 1.0)); }\n\n// A rounded box's outward gradient, by central differences.\nfn bd_grad(q: vec2f, half: vec2f, r: f32) -> vec2f {\n let e = 0.35;\n let g = vec2f(\n sdf_round_box(q + vec2f(e, 0.0), half, r) - sdf_round_box(q - vec2f(e, 0.0), half, r),\n sdf_round_box(q + vec2f(0.0, e), half, r) - sdf_round_box(q - vec2f(0.0, e), half, r));\n let l = length(g);\n return select(vec2f(0.0, -1.0), g / l, l > 1.0e-5);\n}\n\n// ---- the shadows\n\n// A SLAB (a rounded box, `q` in its frame) whose underside is `bottom` and top `top` above the\n// receiver: its footprint cast along the ground slope `S` from every height it spans — each\n// blurred by its own height, the penumbra widening as the slab rises — the union as the max;\n// with the contact shadow at its base while it rests, fading as it floats off. `grow` scales the\n// penumbra's growth (the hand's tools cast sharper); `steps` samples the sweep — enough that the\n// copies never show as steps under that penumbra.\nfn bd_slab(q: vec2f, half: vec2f, r: f32, bottom: f32, top: f32, S: vec2f, k: BoardUniforms, grow: f32, steps: i32) -> f32 {\n var sh = 0.0;\n for (var i = 0; i < steps; i++) {\n let h = mix(bottom, top, f32(i) / f32(steps - 1));\n if (h <= 0.0) { continue; }\n sh = max(sh, bd_blur(sdf_round_box(q - S * h, half, r), k.shadow.x + k.shadow.y * grow * h));\n }\n sh = sh * k.shadow.z;\n let g = max(bottom, 0.0);\n let con = bd_blur(sdf_round_box(q - S * min(g, 1.0) * 0.5, half, r), k.shadow2.x) * k.shadow.w * exp(-g / max(k.shadow2.y, 1.0e-3));\n return max(sh, con);\n}\n\n// ---- the marker\n\n// The tools in the hand cast in the sun's own sharpness — their penumbra grows at this share of the\n// desk objects' rate, their umbra this much firmer — so the shadow of a hovering nib is a clear mark\n// that slides to meet it as it presses: the one cue a hand draws by.\nconst HAND_PENUMBRA = 0.35;\nconst HAND_UMBRA = 1.35;\n\n// THE PEN seen from above, in its own frame: `m` = (u along it from the nib, w across). Its cap\n// slides with `cap`: 0 = on the nib (the marker lying capped), 1 = posted on its back (in the\n// hand), and between, travelling along the barrel as a hand uncaps and posts it. Its ends are\n// ROUND — a domed cap, a round nib, a chamfered plug — so it reads as a moulded thing, not a\n// cut-out. Returns its signed distance, the part the point is on (0 nib · 1 holder · 2 barrel ·\n// 3 stripe · 4 cap · 5 end · 6 the cap's clip · 7 the cap's rim), the local radius, and where\n// the point is on a rounded end (−1 … 0 toward the nib, 0 … 1 toward the back; 0 along the tube).\nfn bd_pen(m: vec2f, cap: f32, nib: f32, k: BoardUniforms) -> vec4f {\n let L = k.pen.x;\n let R = k.pen.y;\n let capL = k.pen.z;\n let u = m.x;\n let w = abs(m.y);\n let nr = max(nib, 0.6);\n let c0 = mix(-2.0, L - capL * 0.55, cap); // the cap: from … to\n let c1 = c0 + capL;\n let u0 = min(0.0, c0);\n let u1 = max(L, c1);\n var r = R;\n var part = 2.0;\n if (u < 3.2) { r = nr; part = 0.0; }\n else if (u < 14.0) { r = mix(nr + 0.8, R * 0.76, smoothstep(3.2, 14.0, u)); part = 1.0; }\n else if (u < 16.5) { r = R * 0.94; }\n else if (u > 44.0 && u < 47.0) { part = 3.0; }\n else if (u > L - 7.0) { r = R * 0.9; part = 5.0; }\n if (u >= c0 && u <= c1) {\n r = R + 0.9; part = 4.0;\n // the rim at the cap's open end (toward the barrel while it is on, toward the nib once posted), the clip along its top\n let atBack = u > c1 - 2.2;\n let atFront = u < c0 + 2.2;\n if (select(atFront, atBack, cap < 0.5)) { part = 7.0; }\n else if (w < 1.15 && u > c0 + 6.0 && u < c1 - 7.0) { part = 6.0; }\n }\n // a rounded box in (u, w): the nearer end's rounding, never past the tube's own radius\n let start = u < (u0 + u1) * 0.5;\n let rc0 = select(nr, R + 0.9, c0 < 0.5);\n let rc1 = select(2.4, R + 0.9, c1 > L - 0.5);\n let rc = min(select(rc1, rc0, start), r);\n let de = select(u - u1, u0 - u, start);\n let dw = w - r;\n let q = vec2f(de, dw) + vec2f(rc);\n let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rc;\n let s = select(clamp((u - (u1 - rc)) / max(rc, 1.0e-3), 0.0, 1.0), -clamp(((u0 + rc) - u) / max(rc, 1.0e-3), 0.0, 1.0), start);\n return vec4f(d, part, r, s);\n}\n\n// The pen's silhouette for its SHADOW: the same tube, its steps smoothed out — a shadow is soft,\n// and a step in the radius (the nib's holder, the cap's lip) would print a line across it.\nfn bd_pen_soft(m: vec2f, cap: f32, nib: f32, k: BoardUniforms) -> f32 {\n let L = k.pen.x;\n let R = k.pen.y;\n let capL = k.pen.z;\n let c0 = mix(-2.0, L - capL * 0.55, cap);\n let c1 = c0 + capL;\n let u0 = min(0.0, c0);\n let u1 = max(L, c1);\n let uu = clamp(m.x, u0, u1);\n let capped = smoothstep(c0 - 3.0, c0 + 3.0, uu) * (1.0 - smoothstep(c1 - 3.0, c1 + 3.0, uu));\n let tube = mix(max(nib, 0.6), R, smoothstep(0.0, 16.0, uu)) - 0.1 * R * smoothstep(L - 10.0, L - 4.0, uu);\n let r = mix(tube, R + 0.9, capped);\n return length(vec2f(m.x - uu, m.y)) - r;\n}\n\n// Its colour at a point of it, seen from above: a tube lying on its side — its across-position\n// turns the normal, and on a rounded end its along-position too; the lamp shades it and the\n// gloss of moulded plastic glints on it; the part picks the material. The clip is a little\n// half-round rod on the cap, lit on the lamp's side.\nfn bd_marker_colour(u: MatUniforms, k: BoardUniforms, sd: vec4f, across: f32, axis: vec2f, perp: vec2f, ink: vec3f, L: vec3f, gobo: f32, noise: f32) -> vec3f {\n var t = clamp(across / max(sd.z, 1.0e-3), -1.0, 1.0);\n if (sd.y == 6.0) { t = clamp(across / 1.15, -1.0, 1.0) * 0.85; }\n let s = sd.w;\n var n = normalize(vec3f(perp * t + axis * s, sqrt(max(1.0 - t * t - s * s, 0.02))));\n var albedo = k.barrel.rgb;\n var gloss = 0.3;\n if (sd.y == 0.0) { albedo = ink * 0.8; gloss = 0.0; } // the felt nib\n else if (sd.y == 1.0) { albedo = k.barrel.rgb * 0.8; } // the tip's holder, a greyer plastic\n else if (sd.y == 3.0) { albedo = ink; } // the stripe that names the ink\n else if (sd.y == 4.0) { albedo = ink; gloss = 0.45; } // the cap\n else if (sd.y == 5.0) { albedo = ink * 0.9; } // the end plug\n else if (sd.y == 6.0) { albedo = mix(ink, k.barrel.rgb, 0.18); gloss = 0.7; } // the clip\n else if (sd.y == 7.0) { albedo = ink * 0.7; } // the cap's rim\n // a small round thing wants more modelling than a flat face: the lamp's fill is less on it\n let lam = 0.32 + 0.68 * min(max(dot(n, L), 0.0) / max(L.z, 0.05), k.light.y);\n albedo = albedo * pow(lam, 1.0 / 2.2);\n let c = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, noise, k.light.z);\n // the gloss: the lamp's glint where it can land, and — where it cannot (a lamp along the axis\n // has no mirror line on a tube) — the bright sky over the desk, a soft stripe along the top\n let toward = L.xy / max(length(L.xy), 1.0e-4);\n let sky = pow(max(dot(n, normalize(vec3f(toward * 0.35, 1.0))), 0.0), 18.0);\n return c + vec3f((bd_glint(n, L, 40.0) + 0.3 * bd_glint(n, L, 8.0) + 0.55 * sky) * gloss * bd_lit(u, k) * mix(0.45, 1.0, gobo));\n}\n\n// ---- the melamine's sheen\n\n// The window the lamp shines through, mirrored in the melamine and seen by an eye over the\n// board: a soft disc of sky toward the lamp, high up; Schlick's Fresnel on melamine's f0. The\n// eye rides the pointer (`B.sheen`), so the reflection slides as the hand moves.\nfn bd_sheen(B: Board, k: BoardUniforms, p: vec2f, n: vec3f) -> f32 {\n let eye = vec3f(B.centre + B.sheen * B.half, k.sheen.z * 2.0 * B.half.y);\n let V = normalize(eye - vec3f(p, 0.0));\n let R = reflect(-V, n);\n let toward = B.lamp.xy / max(length(B.lamp.xy), 1.0e-4);\n let W = normalize(vec3f(toward * 0.42, 0.91));\n let w = smoothstep(cos(k.sheen.y), 1.0, dot(R, W));\n let f = 0.045 + 0.955 * pow(1.0 - max(dot(n, V), 0.0), 5.0);\n return w * w * (f / 0.045);\n}\n\n// ---- the board\n\n// One board at a world point `p`: premultiplied colour. `px` is world units per DEVICE px, `css`\n// world units per CSS px; `frag` the framebuffer pixel (the blue noise's key).\nfn shade_board(B: Board, u: MatUniforms, k: BoardUniforms, p: vec2f, px: f32, css: f32, frag: vec2f,\n gobo_tex: texture_2d<f32>, gobo_samp: sampler, noise_tex: texture_2d<f32>, noise_samp: sampler) -> vec4f {\n let q = p - B.centre;\n let L = B.lamp.xyz;\n let S = B.slope;\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n let dOut = sdf_round_box(q, B.half, B.radius);\n var acc = vec4f(0.0);\n // what a shadow is made of: the world's darkest value by day (the design's cast), the dark's own grey by night\n let shadowInk = mix(k.castTint.rgb, bd_encode(u.eigengrau.xyz), clamp(u.night.x, 0.0, 1.0));\n\n // The slab's shadow on the mat, outside its footprint.\n if (dOut > -px) {\n let sh = bd_slab(q, B.half, B.radius, B.lift, B.lift + B.thick, S, k, 1.0, 6) * (1.0 - bd_cov(dOut, px));\n acc = vec4f(shadowInk * sh, sh);\n }\n\n let top = B.lift + B.thick; // the frame's top off the mat\n let face = top - B.recess; // the melamine's\n if (dOut < px) {\n let cov = bd_cov(dOut, px);\n let dIn = sdf_round_box(q, B.inner, B.innerR);\n var col: vec3f;\n if (dIn > 0.0) {\n // THE FRAME — a rounded bar: across the band the rims roll away, outward at the edge,\n // inward at the lip; brushed along its length; glinting where it turns to the lamp.\n let t = clamp(dIn / max(dIn - dOut, 1.0e-4), 0.0, 1.0);\n let e = 2.0 * t - 1.0;\n // a half-round bar: the normal turns steadily across it, steepest at the rims\n let theta = k.lip.w * sign(e) * pow(abs(e), 0.85);\n let g = bd_grad(q, B.half, B.radius);\n let n = normalize(vec3f(g * sin(theta), cos(theta)));\n let along = dot(q, vec2f(-g.y, g.x));\n let across = dot(q, g);\n let brushed = (felt_noise(vec2f(along * 0.045, across * 2.2)) + 0.5 * felt_noise(vec2f(along * 0.13 + 7.0, across * 6.1))) * k.lip.z;\n var albedo = B.metal.rgb + vec3f(brushed);\n albedo = albedo * pow(bd_lambert(n, L, k), 1.0 / 2.2);\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, top), bn.z);\n col = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n // the metal: a tight glint where the bar turns the lamp to the eye, and a broad sheen round it\n let lit = bd_lit(u, k) * mix(0.2, 1.0, gobo);\n col += vec3f((bd_glint(n, L, 90.0) + 0.6 * bd_glint(n, L, 14.0)) * k.glint.x * lit);\n // metal mirrors the sky: a face turned up and out catches it — the bar's shaded half is never dead\n col += vec3f(0.06 * smoothstep(0.2, 1.0, n.z) * bd_lit(u, k));\n } else {\n // THE MELAMINE — the raster over it: uv across the face, the ink's mip by the screen's density.\n let uv = (q + B.inner) / (2.0 * B.inner);\n let lod = max(log2(max(B.density * px / max(B.scale, 1.0e-3), 1.0e-6)), 0.0);\n var ink = board_ink(B, uv, lod); // (the entry's: where this board's ink is bound — K6a)\n if (B.laying > 0.5) {\n // the stroke being laid, shown as it will land\n let sc = board_stroke(B, uv);\n if (B.tool.w < 0.5) { ink = vec4f(ink.rgb * (1.0 - sc) + B.tool.rgb * sc, ink.a * (1.0 - sc) + sc); }\n else { ink = ink * (1.0 - sc); }\n }\n let cov2 = ink.a;\n var wet = 0.0;\n if (B.wet > 0.5) { wet = board_wet(B, uv) * smoothstep(0.0, 0.3, cov2); }\n\n // the face's normal: a slow waviness, and the ink film standing a few microns proud of it\n let dt = exp2(lod) / B.texels;\n let ax = board_ink(B, uv + vec2f(dt.x, 0.0), lod).a - board_ink(B, uv - vec2f(dt.x, 0.0), lod).a;\n let ay = board_ink(B, uv + vec2f(0.0, dt.y), lod).a - board_ink(B, uv - vec2f(0.0, dt.y), lod).a;\n let w1 = felt_noised(q * (1.0 / 80.0) + vec2f(3.1, 7.7)).yz;\n let w2 = felt_noised(q * (1.0 / 31.0) + vec2f(11.0, 5.0)).yz;\n let slope = (w1 + 0.5 * w2) * 0.006 + vec2f(ax, ay) * k.mel.w;\n let n = normalize(vec3f(-slope, 1.0));\n\n // the albedo: the melamine's tone and grain, the ghost of old erasures, the ink as a filter (linear) over it\n let tone = felt_noise(q * (1.0 / 140.0) + vec2f(20.0, 4.0)) * k.mel.x;\n let grain = (bn.x - 0.5) * k.mel.y;\n let ghost = smoothstep(0.1, 0.45, felt_fbm(q * (1.0 / 21.0) + vec2f(40.0, 3.0))) * k.mel.z;\n let base = (B.surface.rgb * (1.0 + tone) + vec3f(grain)) * (1.0 - ghost);\n let filt = vec3f(1.0 - cov2) + ink.rgb * mix(1.0, k.sheen.w, wet);\n var albedo = bd_encode(srgb_to_linear(clamp(base, vec3f(0.0), vec3f(1.0))) * filt);\n\n // the light on it: the lamp over the ink's relief, the frame's lip on the lamp's side, the corner's occlusion\n let dl = sdf_round_box(q - S * B.recess, B.inner, B.innerR);\n let lip = bd_blur(-dl, k.lip.y) * k.lip.x;\n let ao = 1.0 - 0.07 * (1.0 - smoothstep(0.0, 3.0, -dIn));\n albedo = albedo * pow(bd_lambert(n, L, k) * (1.0 - lip) * ao, 1.0 / 2.2);\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face), bn.z);\n col = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n\n // the window in the glossy face — stronger on wet ink; it shows most where the face is darkest\n // (in the leaves' shade, on the ink), as a reflection does\n let sheen = bd_sheen(B, k, p, n) * k.sheen.x * (1.0 + 1.5 * wet) * bd_lit(u, k);\n col = col + vec3f(sheen) * (vec3f(1.0) - col * 0.55);\n }\n acc = bd_over(vec4f(col * cov, cov), acc);\n }\n\n // THE PEN — one marker in whatever pose the host gives it: lying capped on the melamine, in the\n // hand at the pointer with its cap posted, or on its way between (BOARD.md §5). Its shadow first\n // — the axis point at u stands h0 + u·rise over the melamine and casts along the lamp's slope,\n // softened by that height; a lying pen has its contact line — then the pen itself.\n if (B.pen.w > 0.001) {\n let a = vec2f(cos(B.pen.z), sin(B.pen.z));\n let perp = vec2f(-a.y, a.x);\n let tip = B.pen.xy;\n let h0 = B.penPose.x;\n let rise = B.penPose.y;\n let cap = B.penPose.z;\n let nib = B.penPose.w;\n let pres = B.pen.w;\n if (distance(p, tip) < k.pen.x * 1.3 + 90.0) {\n let D = a + S * rise;\n let A0 = tip + S * h0;\n let c0 = mix(-2.0, k.pen.x - k.pen.z * 0.55, cap);\n let tt = clamp(dot(p - A0, D) / max(dot(D, D), 1.0e-6), min(0.0, c0), max(k.pen.x, c0 + k.pen.z));\n let hs = h0 + tt * rise;\n let rs = p - (A0 + D * tt);\n let msd = bd_pen_soft(vec2f(tt, length(rs)), cap, nib, k);\n let shade = bd_blur(msd, k.shadow.x + k.shadow.y * HAND_PENUMBRA * hs) * k.shadow.z * HAND_UMBRA;\n let rc = p - tip;\n let mc = vec2f(dot(rc, a), dot(rc, perp));\n // lying, it touches the melamine along a line: the contact shadow, gone as it lifts\n let lying = exp(-max(h0 - k.pen.y, 0.0) / 2.5) * (1.0 - smoothstep(0.0, 0.05, rise));\n let contact = bd_blur(bd_pen_soft(mc, cap, nib, k) + k.pen.y * 0.45, k.shadow2.x * 1.4) * 0.42 * lying;\n let msh = min(max(shade, contact), 1.0) * pres;\n acc = bd_over(vec4f(shadowInk * msh, msh), acc);\n let sd = bd_pen(mc, cap, nib, k);\n let mcov = bd_cov(sd.x, px) * pres;\n if (mcov > 0.0) {\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face + h0 + max(mc.x, 0.0) * rise + k.pen.y), bn.z);\n let c = bd_marker_colour(u, k, sd, mc.y, a, perp, B.penInk.rgb, L, gobo, bn.y);\n acc = bd_over(vec4f(c * mcov, mcov), acc);\n }\n }\n }\n\n // THE ERASER in the hand: a felt block with a wooden back, square to its angle.\n if (B.eraser.w > 0.001 && distance(p, B.eraser.xy) < 120.0) {\n let ang = k.block2.x;\n let a = vec2f(cos(ang), sin(ang));\n let perp = vec2f(-a.y, a.x);\n let rc = p - B.eraser.xy;\n let qe = vec2f(dot(rc, a), dot(rc, perp));\n let half = k.block.xy;\n let gap = B.eraser.z;\n let hTop = gap + k.block.z + k.block.w;\n let pres = B.eraser.w;\n let sh = min(bd_slab(qe, half, 3.0, gap, hTop, vec2f(dot(S, a), dot(S, perp)), k, HAND_PENUMBRA, 14) * HAND_UMBRA, 1.0) * pres; // the slope, in the block's frame\n acc = bd_over(vec4f(shadowInk * sh, sh), acc);\n let dFelt = sdf_round_box(qe, half, 3.0);\n let fcov = bd_cov(dFelt, px) * pres;\n if (fcov > 0.0) {\n let dWood = sdf_round_box(qe, half - vec2f(1.5), 2.4);\n let gw = bd_grad(qe, half - vec2f(1.5), 2.4);\n let bevel = 1.0 - smoothstep(0.0, 2.2, -dWood);\n let gw3 = vec2f(gw.x * a.x - gw.y * a.y, gw.x * a.y + gw.y * a.x); // back to world axes\n let n = normalize(vec3f(gw3 * sin(bevel * 0.9), cos(bevel * 0.9)));\n let grain = felt_noise(vec2f(qe.x * 0.06, qe.y * 1.3)) * 0.05 + felt_noise(vec2f(qe.x * 0.21, qe.y * 3.7)) * 0.025;\n var albedo = k.felt.rgb;\n if (dWood < 0.0) {\n // the wooden back, its grain along the block, a finger groove routed round its top\n let groove = 1.0 - smoothstep(0.25, 0.9, abs(sdf_round_box(qe, half - vec2f(6.5, 3.4), 1.8)));\n albedo = (k.wood.rgb + vec3f(grain)) * pow(bd_lambert(n, L, k), 1.0 / 2.2) * (1.0 - 0.2 * groove);\n }\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face + hTop), bn.z);\n let c = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n acc = bd_over(vec4f(c * fcov, fcov), acc);\n }\n }\n\n // The sole-selection ring, inside the outer edge, in the theme's select.\n let ring = bd_line(dOut, k.shadow2.z * css, px) * B.ring;\n acc = bd_over(vec4f(k.select.rgb * ring, ring), acc);\n return acc * B.alpha;\n}\n";
|
|
5
5
|
readonly "board/felt.wgsl": "// The FELT's noise (BOARD.md) — a PURE module: the research whiteboard's gradient noise\n// (research/whiteboard noise.wgsl) with its analytic derivatives, an integer hash so it is\n// the same on every GPU, prefixed `felt_` beside the mat's own `hash12` / `value_noise`. The\n// pen's fibre lanes read it in the stamp; the melamine's waviness and orange peel, the\n// frame's brushing and the eraser's wood read it on the desk.\n\nfn felt_hash(p: vec2u) -> u32 {\n var v = p.x * 1664525u + 1013904223u;\n v = v ^ (p.y * 22695477u + 374761393u);\n v = v ^ (v >> 16u);\n v = v * 0x7feb352du;\n v = v ^ (v >> 15u);\n v = v * 0x846ca68bu;\n v = v ^ (v >> 16u);\n return v;\n}\n\nfn felt_grad(i: vec2i) -> vec2f {\n let h = felt_hash(bitcast<vec2u>(i));\n let a = f32(h & 0xffffu) * (6.283185307 / 65536.0);\n return vec2f(cos(a), sin(a));\n}\n\n// Gradient noise with its derivatives: (value, d/dx, d/dy); the value roughly in [−0.7, 0.7].\nfn felt_noised(p: vec2f) -> vec3f {\n let i = vec2i(floor(p));\n let f = fract(p);\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 ga = felt_grad(i);\n let gb = felt_grad(i + vec2i(1, 0));\n let gc = felt_grad(i + vec2i(0, 1));\n let gd = felt_grad(i + vec2i(1, 1));\n let va = dot(ga, f);\n let vb = dot(gb, f - vec2f(1.0, 0.0));\n let vc = dot(gc, f - vec2f(0.0, 1.0));\n let vd = dot(gd, f - vec2f(1.0, 1.0));\n let k = va - vb - vc + vd;\n let v = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * k;\n let d = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)\n + du * (u.yx * k + vec2f(vb, vc) - va);\n return vec3f(v, d.x, d.y);\n}\n\nfn felt_noise(p: vec2f) -> f32 { return felt_noised(p).x; }\n\nfn felt_fbm(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var q = p;\n for (var i = 0; i < 4; i++) {\n v = v + a * felt_noise(q);\n q = q * 2.03 + vec2f(1.7, 9.2);\n a = a * 0.5;\n }\n return v;\n}\n";
|
|
6
6
|
readonly "board/ink.wgsl": "// The INK — fullscreen passes over a board's raster (the research whiteboard's\n// composite.wgsl): lay a finished stroke from the stroke layer into the ink layer, mark it wet,\n// let the wet fade. The ink layer is PREMULTIPLIED LINEAR colour with coverage in alpha, so\n// its mips average correctly and the melamine can read it as a filter.\n\n@group(0) @binding(0) var<uniform> C: InkUniforms;\n@group(0) @binding(1) var stroke: texture_2d<f32>;\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi << 1u) & 2u);\n let y = f32(vi & 2u);\n return vec4f(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);\n}\n\nfn ink_cov(p: vec4f) -> f32 { return textureLoad(stroke, vec2i(p.xy), 0).r; }\n\n// a marker: premultiplied \"over\" into the ink layer (blend one / one-minus-src-alpha)\n@fragment\nfn fs_draw(@builtin(position) p: vec4f) -> @location(0) vec4f {\n let c = ink_cov(p);\n return vec4f(C.color.rgb * c, c);\n}\n\n// the eraser: ink *= 1 − coverage (blend zero / one-minus-src-alpha)\n@fragment\nfn fs_erase(@builtin(position) p: vec4f) -> @location(0) vec4f {\n return vec4f(0.0, 0.0, 0.0, ink_cov(p));\n}\n\n// the wet layer: max(wet, coverage)\n@fragment\nfn fs_wet(@builtin(position) p: vec4f) -> @location(0) vec4f {\n return vec4f(ink_cov(p), 0.0, 0.0, 1.0);\n}\n\n// the wet fades: dst *= the blend constant\n@fragment\nfn fs_dry(@builtin(position) p: vec4f) -> @location(0) vec4f {\n return vec4f(0.0);\n}\n";
|
|
@@ -48,13 +48,15 @@ export interface SelectionMenuAnchor {
|
|
|
48
48
|
* An object is IN HAND (design-015 §8, D4b): the menu travels to the foot of the view and changes role (M1) — one element,
|
|
49
49
|
* not a second toolbar — Send stays first, the kind's tools take the middle, Done ends the bar. `landing`: the object is
|
|
50
50
|
* flying home — the bar steps aside and comes back above the object 200 ms after it lands. `active` (D3t-a): the mode in
|
|
51
|
-
* hand — core's `HeldTool` — the one slot marked.
|
|
51
|
+
* hand — core's `HeldTool` — the one slot marked. `travelMs` (petition I20): the travel the host set on the desk
|
|
52
|
+
* (`deskLayer({ hold: { travelMs } })`) — the bar travels over it, to the foot and back; absent, `SELECTION_MENU.travelMs`.
|
|
52
53
|
*/
|
|
53
54
|
readonly held?: {
|
|
54
55
|
readonly tools: readonly SelectionMenuTool[];
|
|
55
56
|
readonly active?: string;
|
|
56
57
|
readonly landing: boolean;
|
|
57
58
|
readonly settled: boolean;
|
|
59
|
+
readonly travelMs?: number;
|
|
58
60
|
};
|
|
59
61
|
/**
|
|
60
62
|
* The selection's KIND ACTS (design-016 K8a — the desk's `MenuSlot`s, mirrored structurally): what every selected object's type
|
|
@@ -124,7 +126,8 @@ export interface SelectionAction {
|
|
|
124
126
|
}
|
|
125
127
|
/**
|
|
126
128
|
* The menu's numbers (*Marks on the Mat* §14): 40 tall, 10 above the marks, 16 from the sides, 8 from the view's top and foot;
|
|
127
|
-
* away 90 ms, back 200 ms after, in 180 ms. The held bar (§8, D4b): centred in the 72 px band at the foot, the travel 340 ms
|
|
129
|
+
* away 90 ms, back 200 ms after, in 180 ms. The held bar (§8, D4b): centred in the 72 px band at the foot, the travel 340 ms — or the
|
|
130
|
+
* host's, when the anchor carries one (`held.travelMs`, petition I20).
|
|
128
131
|
*/
|
|
129
132
|
export declare const SELECTION_MENU: {
|
|
130
133
|
readonly height: 40;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecook/ice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0-desk.2",
|
|
4
4
|
"description": "ICE — infinite canvas engine. The desk: every object under the camera drawn by one WebGPU renderer from a CRDT-synced ECS world, the DOM in screen space. defineObject turns a GPU kind into a canvas citizen — selectable, movable, nested in mini mats, undoable per gesture, collaborative.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "James Yong <me@jamesyong42.com>",
|