{"version":3,"sources":["icon-light.mjs"],"sourcesContent":["// HOW EVERY ITEM ICON IS LIT (the owner, Oct 4: \"Near lamp\", A8 on the Icon Light Bench, picks/icon-light-item).\n//\n// A small lamp just above and in front of the item, so every face shades across itself and polished metal shows a\n// hotspot. It replaces the old rig (ambient 0.5, a key at (2, 3, 2.5), a blue rim, a fill from the viewer), under which\n// a kube's three faces came out almost alike (Arid Regolith: left 98%, right 93% of the top).\n//\n// The lamp is a point light at NEAR_LAMP.pos in the icon's own units: a kube or a bit of edge 1 (so for a cube drawn at\n// scale s the lamp stands at s x pos), a model in its group's units. It falls off as 1 / (1 + falloff x d^2) in the same\n// units, which three.js's own point light cannot do, so app.js swaps that one shader function (LAMP_FALLOFF_GLSL) and\n// carries `falloff / s^2` in the light's decay.\n//\n// EXPOSURE: a white matte top face, lit at the point (0, 0.3, 0), comes out as bright as it did under the old rig: the\n// bench's own normalisation, so a pick changes how light falls, not how dark the bar is. Machines (painted at full\n// strength on top by the lattice painter) and models (lit without the fill) keep their own level against kubes.\n// Pure numbers here, no THREE, so tests/icon-light.test.mjs can check them.\n\nexport const ICON_LIGHT = 'A9';   // goes into every cached icon's key, so a picture lit the old way is never served (A9: one exposure and the ink, v6)\nexport const NEAR_LAMP = Object.freeze({ ambient: 0.25, color: '#fff4e6', intensity: 3.6, pos: Object.freeze([0.3, 1.0, 0.95]), falloff: 2.2 });\n\nconst srgbToLinear = (c) => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));\nconst hexLinear = (h) => { const n = parseInt(String(h).replace('#', ''), 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255].map((v) => srgbToLinear(v / 255)); };\nconst unit = (v) => { const l = Math.hypot(v[0], v[1], v[2]) || 1; return v.map((x) => x / l); };\n\n// The old rigs, kept only as the exposure reference: irradiance on an upward face, white, over pi (three.js Lambert).\nconst OLD_KEY_UP = unit([2, 3, 2.5])[1], OLD_FILL_UP = unit([1, 1, 1])[1];\nexport const OLD_KUBE_TOP = (0.5 + 1.7 * OLD_KEY_UP + 0.35 * OLD_FILL_UP) / Math.PI;\nexport const OLD_MODEL_TOP = (0.5 + 1.7 * OLD_KEY_UP) / Math.PI;\n\n// The lamp's own top-face brightness at the probe point (0, 0.3, 0), averaged over its colour.\nexport function lampTop(L = NEAR_LAMP) {\n  const p = [0, 0.3, 0], w = [L.pos[0] - p[0], L.pos[1] - p[1], L.pos[2] - p[2]], d2 = w[0] * w[0] + w[1] * w[1] + w[2] * w[2];\n  const nl = Math.max(0, w[1] / Math.sqrt(d2)), att = 1 / (1 + L.falloff * d2), c = hexLinear(L.color);\n  return c.reduce((s, ch) => s + (L.ambient + L.intensity * att * ch * nl), 0) / 3 / Math.PI;\n}\n// The multiplier on the lamp's (and the ambient's) intensity. ONE LAMP FOR EVERY ICON (the owner's play review, Oct 23, v6,\n// the primary: \"every icon under the same lamp, with the same thin dark outline and drop shadow\"): a machine's painted\n// atlas, a model and a kube all at the kube's exposure now (they had three, so a machine's faces came out at full paint\n// and a kube's at its lamp-lit level beside it). `kind` is kept for the callers; it no longer changes the light.\nexport function lampExposure(kind, L = NEAR_LAMP) { void kind; return OLD_KUBE_TOP / lampTop(L); }\n// THE INK EVERY ICON WEARS (v6; app.js iconBlit): a thin dark outline round its silhouette and a soft drop shadow under it,\n// in px of an icon canvas `w` wide.\nexport const ICON_INK = Object.freeze({ outline: 'rgba(8,10,14,0.9)', shadowAlpha: 0.16, outlinePx: (w) => Math.max(1, w / 80), shadowPx: (w) => Math.max(1.5, w / 36) });\n// Where the lamp stands and how it falls off for an icon drawn at scale s (1 for a model).\nexport function lampAt(s = 1, L = NEAR_LAMP) { return { pos: L.pos.map((v) => v * s), decay: L.falloff / (s * s) }; }\n\n// three.js's getDistanceAttenuation, replaced for icon materials only: the decay carries the falloff.\nexport const LAMP_FALLOFF_GLSL = 'float getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\\n\\treturn 1.0 / ( 1.0 + decayExponent * lightDistance * lightDistance );\\n}';\nexport const THREE_FALLOFF_RE = /float getDistanceAttenuation\\( const in float lightDistance, const in float cutoffDistance, const in float decayExponent \\) \\{[\\s\\S]*?return distanceFalloff;\\s*\\}/;\n"],"mappings":"AAgBO,aAAM,WAAa,KACb,UAAY,OAAO,OAAO,CAAE,QAAS,IAAM,MAAO,UAAW,UAAW,IAAK,IAAK,OAAO,OAAO,CAAC,GAAK,EAAK,GAAI,CAAC,EAAG,QAAS,GAAI,CAAC,EAE9I,MAAM,aAAgB,GAAO,GAAK,OAAU,EAAI,MAAQ,KAAK,KAAK,EAAI,MAAS,MAAO,GAAG,EACnF,UAAa,GAAM,CAAE,MAAM,EAAI,SAAS,OAAO,CAAC,EAAE,QAAQ,IAAK,EAAE,EAAG,EAAE,EAAG,MAAO,CAAE,GAAK,GAAM,IAAM,GAAK,EAAK,IAAK,EAAI,GAAG,EAAE,IAAK,GAAM,aAAa,EAAI,GAAG,CAAC,CAAG,EAC9J,KAAQ,GAAM,CAAE,MAAM,EAAI,KAAK,MAAM,EAAE,CAAC,EAAG,EAAE,CAAC,EAAG,EAAE,CAAC,CAAC,GAAK,EAAG,OAAO,EAAE,IAAK,GAAM,EAAI,CAAC,CAAG,EAGzF,WAAa,KAAK,CAAC,EAAG,EAAG,GAAG,CAAC,EAAE,CAAC,EAAG,YAAc,KAAK,CAAC,EAAG,EAAG,CAAC,CAAC,EAAE,CAAC,EACjE,aAAM,cAAgB,GAAM,IAAM,WAAa,IAAO,aAAe,KAAK,GACpE,eAAiB,GAAM,IAAM,YAAc,KAAK,GAGtD,gBAAS,QAAQ,EAAI,UAAW,CACrC,MAAM,EAAI,CAAC,EAAG,GAAK,CAAC,EAAG,EAAI,CAAC,EAAE,IAAI,CAAC,EAAI,EAAE,CAAC,EAAG,EAAE,IAAI,CAAC,EAAI,EAAE,CAAC,EAAG,EAAE,IAAI,CAAC,EAAI,EAAE,CAAC,CAAC,EAAG,GAAK,EAAE,CAAC,EAAI,EAAE,CAAC,EAAI,EAAE,CAAC,EAAI,EAAE,CAAC,EAAI,EAAE,CAAC,EAAI,EAAE,CAAC,EACrH,GAAK,KAAK,IAAI,EAAG,EAAE,CAAC,EAAI,KAAK,KAAK,EAAE,CAAC,EAAG,IAAM,GAAK,EAAI,EAAE,QAAU,IACzE,OADkF,UAAU,EAAE,KAAK,EAC1F,OAAO,CAAC,EAAG,KAAO,GAAK,EAAE,QAAU,EAAE,UAAY,IAAM,GAAK,IAAK,CAAC,EAAI,EAAI,KAAK,EAC1F,CAKO,gBAAS,aAAa,KAAM,EAAI,UAAW,CAAa,OAAO,aAAe,QAAQ,CAAC,CAAG,CAG1F,aAAM,SAAW,OAAO,OAAO,CAAE,QAAS,oBAAqB,YAAa,IAAM,UAAY,GAAM,KAAK,IAAI,EAAG,EAAI,EAAE,EAAG,SAAW,GAAM,KAAK,IAAI,IAAK,EAAI,EAAE,CAAE,CAAC,EAEjK,gBAAS,OAAO,EAAI,EAAG,EAAI,UAAW,CAAE,MAAO,CAAE,IAAK,EAAE,IAAI,IAAK,GAAM,EAAI,CAAC,EAAG,MAAO,EAAE,SAAW,EAAI,EAAG,CAAG,CAG7G,aAAM,kBAAoB;AAAA;AAAA,GACpB,iBAAmB","names":[],"file":"icon-light.mjs"}