interactive-bg 0.0.0-stage → 0.1.1

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.
Files changed (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -2
  3. package/dist/core/canvas2d-renderer.cjs +119 -0
  4. package/dist/core/canvas2d-renderer.cjs.map +1 -0
  5. package/dist/core/canvas2d-renderer.d.cts +7 -0
  6. package/dist/core/canvas2d-renderer.d.ts +7 -0
  7. package/dist/core/canvas2d-renderer.js +119 -0
  8. package/dist/core/canvas2d-renderer.js.map +1 -0
  9. package/dist/core/color.cjs +46 -0
  10. package/dist/core/color.cjs.map +1 -0
  11. package/dist/core/color.d.cts +6 -0
  12. package/dist/core/color.d.ts +6 -0
  13. package/dist/core/color.js +46 -0
  14. package/dist/core/color.js.map +1 -0
  15. package/dist/core/engine.cjs +367 -0
  16. package/dist/core/engine.cjs.map +1 -0
  17. package/dist/core/engine.d.cts +69 -0
  18. package/dist/core/engine.d.ts +69 -0
  19. package/dist/core/engine.js +366 -0
  20. package/dist/core/engine.js.map +1 -0
  21. package/dist/core/style.cjs +21 -0
  22. package/dist/core/style.cjs.map +1 -0
  23. package/dist/core/style.d.cts +85 -0
  24. package/dist/core/style.d.ts +85 -0
  25. package/dist/core/style.js +21 -0
  26. package/dist/core/style.js.map +1 -0
  27. package/dist/core/surface.cjs +25 -0
  28. package/dist/core/surface.cjs.map +1 -0
  29. package/dist/core/surface.d.cts +42 -0
  30. package/dist/core/surface.d.ts +42 -0
  31. package/dist/core/surface.js +24 -0
  32. package/dist/core/surface.js.map +1 -0
  33. package/dist/core/webgl-renderer.cjs +137 -0
  34. package/dist/core/webgl-renderer.cjs.map +1 -0
  35. package/dist/core/webgl-renderer.d.cts +8 -0
  36. package/dist/core/webgl-renderer.d.ts +8 -0
  37. package/dist/core/webgl-renderer.js +137 -0
  38. package/dist/core/webgl-renderer.js.map +1 -0
  39. package/dist/core/webgl.cjs +62 -0
  40. package/dist/core/webgl.cjs.map +1 -0
  41. package/dist/core/webgl.d.cts +15 -0
  42. package/dist/core/webgl.d.ts +15 -0
  43. package/dist/core/webgl.js +60 -0
  44. package/dist/core/webgl.js.map +1 -0
  45. package/dist/dots/canvas2d.cjs +14 -0
  46. package/dist/dots/canvas2d.cjs.map +1 -0
  47. package/dist/dots/canvas2d.d.cts +5 -0
  48. package/dist/dots/canvas2d.d.ts +5 -0
  49. package/dist/dots/canvas2d.js +13 -0
  50. package/dist/dots/canvas2d.js.map +1 -0
  51. package/dist/dots/index.cjs +16 -0
  52. package/dist/dots/index.cjs.map +1 -0
  53. package/dist/dots/index.d.cts +5 -0
  54. package/dist/dots/index.d.ts +5 -0
  55. package/dist/dots/index.js +15 -0
  56. package/dist/dots/index.js.map +1 -0
  57. package/dist/dots/webgl.cjs +14 -0
  58. package/dist/dots/webgl.cjs.map +1 -0
  59. package/dist/dots/webgl.d.cts +5 -0
  60. package/dist/dots/webgl.d.ts +5 -0
  61. package/dist/dots/webgl.js +13 -0
  62. package/dist/dots/webgl.js.map +1 -0
  63. package/dist/index.cjs +24 -0
  64. package/dist/index.d.cts +18 -0
  65. package/dist/index.d.ts +18 -0
  66. package/dist/index.js +11 -0
  67. package/dist/index.umd.js +211 -0
  68. package/dist/index.umd.js.map +1 -0
  69. package/dist/plus/canvas2d.cjs +14 -0
  70. package/dist/plus/canvas2d.cjs.map +1 -0
  71. package/dist/plus/canvas2d.d.cts +5 -0
  72. package/dist/plus/canvas2d.d.ts +5 -0
  73. package/dist/plus/canvas2d.js +13 -0
  74. package/dist/plus/canvas2d.js.map +1 -0
  75. package/dist/plus/index.cjs +16 -0
  76. package/dist/plus/index.cjs.map +1 -0
  77. package/dist/plus/index.d.cts +5 -0
  78. package/dist/plus/index.d.ts +5 -0
  79. package/dist/plus/index.js +15 -0
  80. package/dist/plus/index.js.map +1 -0
  81. package/dist/plus/webgl.cjs +14 -0
  82. package/dist/plus/webgl.cjs.map +1 -0
  83. package/dist/plus/webgl.d.cts +5 -0
  84. package/dist/plus/webgl.d.ts +5 -0
  85. package/dist/plus/webgl.js +13 -0
  86. package/dist/plus/webgl.js.map +1 -0
  87. package/dist/shared/buffer.cjs +20 -0
  88. package/dist/shared/buffer.cjs.map +1 -0
  89. package/dist/shared/buffer.d.cts +11 -0
  90. package/dist/shared/buffer.d.ts +11 -0
  91. package/dist/shared/buffer.js +19 -0
  92. package/dist/shared/buffer.js.map +1 -0
  93. package/dist/shared/grid-webgl.cjs +63 -0
  94. package/dist/shared/grid-webgl.cjs.map +1 -0
  95. package/dist/shared/grid-webgl.d.cts +10 -0
  96. package/dist/shared/grid-webgl.d.ts +10 -0
  97. package/dist/shared/grid-webgl.js +61 -0
  98. package/dist/shared/grid-webgl.js.map +1 -0
  99. package/dist/shared/grid.cjs +133 -0
  100. package/dist/shared/grid.cjs.map +1 -0
  101. package/dist/shared/grid.d.cts +72 -0
  102. package/dist/shared/grid.d.ts +72 -0
  103. package/dist/shared/grid.js +123 -0
  104. package/dist/shared/grid.js.map +1 -0
  105. package/dist/shared/noise.cjs +166 -0
  106. package/dist/shared/noise.cjs.map +1 -0
  107. package/dist/shared/noise.d.cts +4 -0
  108. package/dist/shared/noise.d.ts +4 -0
  109. package/dist/shared/noise.js +166 -0
  110. package/dist/shared/noise.js.map +1 -0
  111. package/dist/styles/dots/canvas2d.cjs +99 -0
  112. package/dist/styles/dots/canvas2d.cjs.map +1 -0
  113. package/dist/styles/dots/canvas2d.d.cts +7 -0
  114. package/dist/styles/dots/canvas2d.d.ts +7 -0
  115. package/dist/styles/dots/canvas2d.js +99 -0
  116. package/dist/styles/dots/canvas2d.js.map +1 -0
  117. package/dist/styles/dots/definition.cjs +15 -0
  118. package/dist/styles/dots/definition.cjs.map +1 -0
  119. package/dist/styles/dots/definition.d.cts +7 -0
  120. package/dist/styles/dots/definition.d.ts +7 -0
  121. package/dist/styles/dots/definition.js +16 -0
  122. package/dist/styles/dots/definition.js.map +1 -0
  123. package/dist/styles/dots/motion.cjs +6 -0
  124. package/dist/styles/dots/motion.cjs.map +1 -0
  125. package/dist/styles/dots/motion.d.cts +2 -0
  126. package/dist/styles/dots/motion.d.ts +2 -0
  127. package/dist/styles/dots/motion.js +6 -0
  128. package/dist/styles/dots/motion.js.map +1 -0
  129. package/dist/styles/dots/options.cjs +33 -0
  130. package/dist/styles/dots/options.cjs.map +1 -0
  131. package/dist/styles/dots/options.d.cts +33 -0
  132. package/dist/styles/dots/options.d.ts +33 -0
  133. package/dist/styles/dots/options.js +32 -0
  134. package/dist/styles/dots/options.js.map +1 -0
  135. package/dist/styles/dots/webgl.cjs +113 -0
  136. package/dist/styles/dots/webgl.cjs.map +1 -0
  137. package/dist/styles/dots/webgl.d.cts +4 -0
  138. package/dist/styles/dots/webgl.d.ts +4 -0
  139. package/dist/styles/dots/webgl.js +113 -0
  140. package/dist/styles/dots/webgl.js.map +1 -0
  141. package/dist/styles/plus/canvas2d.cjs +88 -0
  142. package/dist/styles/plus/canvas2d.cjs.map +1 -0
  143. package/dist/styles/plus/canvas2d.d.cts +8 -0
  144. package/dist/styles/plus/canvas2d.d.ts +8 -0
  145. package/dist/styles/plus/canvas2d.js +88 -0
  146. package/dist/styles/plus/canvas2d.js.map +1 -0
  147. package/dist/styles/plus/definition.cjs +17 -0
  148. package/dist/styles/plus/definition.cjs.map +1 -0
  149. package/dist/styles/plus/definition.d.cts +9 -0
  150. package/dist/styles/plus/definition.d.ts +9 -0
  151. package/dist/styles/plus/definition.js +18 -0
  152. package/dist/styles/plus/definition.js.map +1 -0
  153. package/dist/styles/plus/motion.cjs +18 -0
  154. package/dist/styles/plus/motion.cjs.map +1 -0
  155. package/dist/styles/plus/motion.d.cts +5 -0
  156. package/dist/styles/plus/motion.d.ts +5 -0
  157. package/dist/styles/plus/motion.js +18 -0
  158. package/dist/styles/plus/motion.js.map +1 -0
  159. package/dist/styles/plus/options.cjs +45 -0
  160. package/dist/styles/plus/options.cjs.map +1 -0
  161. package/dist/styles/plus/options.d.cts +45 -0
  162. package/dist/styles/plus/options.d.ts +45 -0
  163. package/dist/styles/plus/options.js +43 -0
  164. package/dist/styles/plus/options.js.map +1 -0
  165. package/dist/styles/plus/turns.cjs +102 -0
  166. package/dist/styles/plus/turns.cjs.map +1 -0
  167. package/dist/styles/plus/turns.d.cts +17 -0
  168. package/dist/styles/plus/turns.d.ts +17 -0
  169. package/dist/styles/plus/turns.js +102 -0
  170. package/dist/styles/plus/turns.js.map +1 -0
  171. package/dist/styles/plus/webgl.cjs +121 -0
  172. package/dist/styles/plus/webgl.cjs.map +1 -0
  173. package/dist/styles/plus/webgl.d.cts +8 -0
  174. package/dist/styles/plus/webgl.d.ts +8 -0
  175. package/dist/styles/plus/webgl.js +121 -0
  176. package/dist/styles/plus/webgl.js.map +1 -0
  177. package/dist/styles/topo/canvas2d.cjs +77 -0
  178. package/dist/styles/topo/canvas2d.cjs.map +1 -0
  179. package/dist/styles/topo/canvas2d.d.cts +4 -0
  180. package/dist/styles/topo/canvas2d.d.ts +4 -0
  181. package/dist/styles/topo/canvas2d.js +77 -0
  182. package/dist/styles/topo/canvas2d.js.map +1 -0
  183. package/dist/styles/topo/contours.cjs +83 -0
  184. package/dist/styles/topo/contours.cjs.map +1 -0
  185. package/dist/styles/topo/contours.d.cts +9 -0
  186. package/dist/styles/topo/contours.d.ts +9 -0
  187. package/dist/styles/topo/contours.js +83 -0
  188. package/dist/styles/topo/contours.js.map +1 -0
  189. package/dist/styles/topo/definition.cjs +13 -0
  190. package/dist/styles/topo/definition.cjs.map +1 -0
  191. package/dist/styles/topo/definition.d.cts +7 -0
  192. package/dist/styles/topo/definition.d.ts +7 -0
  193. package/dist/styles/topo/definition.js +14 -0
  194. package/dist/styles/topo/definition.js.map +1 -0
  195. package/dist/styles/topo/field.cjs +118 -0
  196. package/dist/styles/topo/field.cjs.map +1 -0
  197. package/dist/styles/topo/field.d.cts +26 -0
  198. package/dist/styles/topo/field.d.ts +26 -0
  199. package/dist/styles/topo/field.js +118 -0
  200. package/dist/styles/topo/field.js.map +1 -0
  201. package/dist/styles/topo/options.cjs +39 -0
  202. package/dist/styles/topo/options.cjs.map +1 -0
  203. package/dist/styles/topo/options.d.cts +52 -0
  204. package/dist/styles/topo/options.d.ts +52 -0
  205. package/dist/styles/topo/options.js +38 -0
  206. package/dist/styles/topo/options.js.map +1 -0
  207. package/dist/styles/topo/terrain.cjs +22 -0
  208. package/dist/styles/topo/terrain.cjs.map +1 -0
  209. package/dist/styles/topo/terrain.d.cts +11 -0
  210. package/dist/styles/topo/terrain.d.ts +11 -0
  211. package/dist/styles/topo/terrain.js +22 -0
  212. package/dist/styles/topo/terrain.js.map +1 -0
  213. package/dist/styles/topo/webgl.cjs +150 -0
  214. package/dist/styles/topo/webgl.cjs.map +1 -0
  215. package/dist/styles/topo/webgl.d.cts +7 -0
  216. package/dist/styles/topo/webgl.d.ts +7 -0
  217. package/dist/styles/topo/webgl.js +150 -0
  218. package/dist/styles/topo/webgl.js.map +1 -0
  219. package/dist/topo/canvas2d.cjs +14 -0
  220. package/dist/topo/canvas2d.cjs.map +1 -0
  221. package/dist/topo/canvas2d.d.cts +5 -0
  222. package/dist/topo/canvas2d.d.ts +5 -0
  223. package/dist/topo/canvas2d.js +13 -0
  224. package/dist/topo/canvas2d.js.map +1 -0
  225. package/dist/topo/index.cjs +16 -0
  226. package/dist/topo/index.cjs.map +1 -0
  227. package/dist/topo/index.d.cts +5 -0
  228. package/dist/topo/index.d.ts +5 -0
  229. package/dist/topo/index.js +15 -0
  230. package/dist/topo/index.js.map +1 -0
  231. package/dist/topo/webgl.cjs +14 -0
  232. package/dist/topo/webgl.cjs.map +1 -0
  233. package/dist/topo/webgl.d.cts +5 -0
  234. package/dist/topo/webgl.d.ts +5 -0
  235. package/dist/topo/webgl.js +13 -0
  236. package/dist/topo/webgl.js.map +1 -0
  237. package/package.json +192 -4
@@ -0,0 +1,102 @@
1
+ import { waveEnvelope, waveRing } from "../../shared/grid.js";
2
+ import { facingAngle } from "./motion.js";
3
+ // src/styles/plus/turns.ts
4
+ var QUARTER = Math.PI / 2;
5
+ /** A sign has finished turning once its arm tips are this close to where they're heading, in CSS pixels. */
6
+ var SETTLED_PX = .05;
7
+ /** The same turn, expressed within ±45° (a "+" looks the same every 90°). */
8
+ function wrap(angle) {
9
+ return angle - QUARTER * Math.round(angle / QUARTER);
10
+ }
11
+ function createSignTurns() {
12
+ let data = /* @__PURE__ */ new Float32Array(0);
13
+ const waveData = /* @__PURE__ */ new Float32Array(32);
14
+ /** Work out each wave's ring radius and envelope once per frame instead of once per sign. */
15
+ function packWaves(waves, speed, duration) {
16
+ for (let k = 0; k < waves.length; k++) {
17
+ const wave = waves[k];
18
+ waveData[k * 4] = wave.x;
19
+ waveData[k * 4 + 1] = wave.y;
20
+ waveData[k * 4 + 2] = speed * wave.age;
21
+ waveData[k * 4 + 3] = waveEnvelope(wave.age, duration);
22
+ }
23
+ }
24
+ let changed = false;
25
+ let settling = false;
26
+ let lastX = NaN;
27
+ let lastY = NaN;
28
+ let lastAlign = NaN;
29
+ let lastOriginX = NaN;
30
+ let lastOriginY = NaN;
31
+ let lastOptions = null;
32
+ let hadWaves = false;
33
+ return {
34
+ get data() {
35
+ return data;
36
+ },
37
+ get changed() {
38
+ return changed;
39
+ },
40
+ get settling() {
41
+ return settling;
42
+ },
43
+ update(frame, options, grid, waves) {
44
+ const { cols, rows, originX, originY } = grid;
45
+ const { spacing, waveSpeed, waveWidth, waveDuration, waveAlign } = options;
46
+ const resized = data.length !== cols * rows * 2;
47
+ if (resized) data = new Float32Array(cols * rows * 2);
48
+ const align = options.pointerAlign * frame.pointer.influence;
49
+ const { x: px, y: py } = frame.pointer;
50
+ const unchanged = !resized && !settling && !hadWaves && waves.length === 0 && align === lastAlign && (align === 0 || px === lastX && py === lastY && originX === lastOriginX && originY === lastOriginY && options === lastOptions);
51
+ hadWaves = waves.length > 0;
52
+ if (unchanged) {
53
+ changed = false;
54
+ return;
55
+ }
56
+ lastX = px;
57
+ lastY = py;
58
+ lastAlign = align;
59
+ lastOriginX = originX;
60
+ lastOriginY = originY;
61
+ lastOptions = options;
62
+ const waveCount = waves.length;
63
+ packWaves(waves, waveSpeed, waveDuration);
64
+ const waveReach = 3 * waveWidth;
65
+ const settled = SETTLED_PX / options.size;
66
+ const ease = options.turnTime > 0 ? 1 - Math.exp(-frame.dt / options.turnTime) : 1;
67
+ settling = false;
68
+ for (let j = 0; j < rows; j++) {
69
+ const restY = originY + j * spacing;
70
+ for (let i = 0; i < cols; i++) {
71
+ const restX = originX + i * spacing;
72
+ let target = 0;
73
+ if (align !== 0) target = align * facingAngle(restX - px, restY - py);
74
+ let ring = 0;
75
+ for (let w = 0; w < waveCount * 4; w += 4) {
76
+ const dx = restX - waveData[w];
77
+ const dy = restY - waveData[w + 1];
78
+ const outer = waveData[w + 2] + waveReach;
79
+ if (Math.abs(dx) > outer || Math.abs(dy) > outer) continue;
80
+ const distance = Math.sqrt(dx * dx + dy * dy);
81
+ if (Math.abs(distance - waveData[w + 2]) > waveReach) continue;
82
+ const height = waveRing(distance, waveData[w + 2], waveWidth) * waveData[w + 3];
83
+ ring += height;
84
+ target += waveAlign * Math.min(1, height) * facingAngle(dx, dy);
85
+ }
86
+ const k = (j * cols + i) * 2;
87
+ const remaining = wrap(target - data[k]);
88
+ if (Math.abs(remaining) < settled) data[k] = wrap(target);
89
+ else {
90
+ data[k] = wrap(data[k] + remaining * ease);
91
+ settling = true;
92
+ }
93
+ data[k + 1] = ring;
94
+ }
95
+ }
96
+ changed = true;
97
+ }
98
+ };
99
+ }
100
+ export { createSignTurns };
101
+
102
+ //# sourceMappingURL=turns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turns.js","names":[],"sources":["../../../src/styles/plus/turns.ts"],"sourcesContent":["import type { Frame } from '../../core/style';\nimport { MAX_WAVES, waveEnvelope, waveRing, type Grid, type Wave } from '../../shared/grid';\nimport { facingAngle } from './motion';\nimport type { ResolvedPlusOptions } from './options';\n\nconst QUARTER = Math.PI / 2;\n/** A sign has finished turning once its arm tips are this close to where they're heading, in CSS pixels. */\nconst SETTLED_PX = 0.05;\n\n/** The same turn, expressed within ±45° (a \"+\" looks the same every 90°). */\nfunction wrap(angle: number): number {\n return angle - QUARTER * Math.round(angle / QUARTER);\n}\n\n/**\n * Each sign's angle and wave-crest height, kept between frames so signs ease\n * towards the angle they want instead of jumping to it. Both renderers use it.\n */\nexport interface SignTurns {\n /** Angle (radians) and wave-crest height of every sign, row-major, as pairs. */\n readonly data: Float32Array<ArrayBuffer>;\n /** Whether `data` changed in the last update. */\n readonly changed: boolean;\n /** Whether any sign is still turning towards its target. */\n readonly settling: boolean;\n update(frame: Frame, options: ResolvedPlusOptions, grid: Grid, waves: readonly Wave[]): void;\n}\n\nexport function createSignTurns(): SignTurns {\n let data = new Float32Array(0);\n const waveData = new Float32Array(MAX_WAVES * 4);\n\n /** Work out each wave's ring radius and envelope once per frame instead of once per sign. */\n function packWaves(waves: readonly Wave[], speed: number, duration: number): void {\n for (let k = 0; k < waves.length; k++) {\n const wave = waves[k];\n waveData[k * 4] = wave.x;\n waveData[k * 4 + 1] = wave.y;\n waveData[k * 4 + 2] = speed * wave.age;\n waveData[k * 4 + 3] = waveEnvelope(wave.age, duration);\n }\n }\n let changed = false;\n let settling = false;\n // What the last update computed from: if none of it changes, neither do the angles.\n let lastX = NaN;\n let lastY = NaN;\n let lastAlign = NaN;\n let lastOriginX = NaN;\n let lastOriginY = NaN;\n let lastOptions: ResolvedPlusOptions | null = null;\n let hadWaves = false;\n\n return {\n get data() {\n return data;\n },\n get changed() {\n return changed;\n },\n get settling() {\n return settling;\n },\n update(frame, options, grid, waves) {\n const { cols, rows, originX, originY } = grid;\n const { spacing, waveSpeed, waveWidth, waveDuration, waveAlign } = options;\n const resized = data.length !== cols * rows * 2;\n if (resized) data = new Float32Array(cols * rows * 2);\n\n const align = options.pointerAlign * frame.pointer.influence;\n const { x: px, y: py } = frame.pointer;\n // Nothing is turning and nothing it depends on has moved: the angles are already right.\n // With no pointer, every sign is upright whatever the grid or options.\n const unchanged =\n !resized &&\n !settling &&\n !hadWaves &&\n waves.length === 0 &&\n align === lastAlign &&\n (align === 0 ||\n (px === lastX && py === lastY && originX === lastOriginX && originY === lastOriginY && options === lastOptions));\n hadWaves = waves.length > 0;\n if (unchanged) {\n changed = false;\n return;\n }\n lastX = px;\n lastY = py;\n lastAlign = align;\n lastOriginX = originX;\n lastOriginY = originY;\n lastOptions = options;\n\n // Each wave as x, y, ring radius, envelope. Further than 3 widths from its ring a wave is below e⁻⁹: skipped.\n const waveCount = waves.length;\n packWaves(waves, waveSpeed, waveDuration);\n const waveReach = 3 * waveWidth;\n const settled = SETTLED_PX / options.size;\n const ease = options.turnTime > 0 ? 1 - Math.exp(-frame.dt / options.turnTime) : 1;\n settling = false;\n\n for (let j = 0; j < rows; j++) {\n const restY = originY + j * spacing;\n for (let i = 0; i < cols; i++) {\n const restX = originX + i * spacing;\n\n let target = 0;\n // While the pointer is over the background, every sign turns to face it.\n if (align !== 0) target = align * facingAngle(restX - px, restY - py);\n\n let ring = 0;\n for (let w = 0; w < waveCount * 4; w += 4) {\n const dx = restX - waveData[w];\n const dy = restY - waveData[w + 1];\n const outer = waveData[w + 2] + waveReach;\n if (Math.abs(dx) > outer || Math.abs(dy) > outer) continue;\n const distance = Math.sqrt(dx * dx + dy * dy);\n if (Math.abs(distance - waveData[w + 2]) > waveReach) continue;\n const height = waveRing(distance, waveData[w + 2], waveWidth) * waveData[w + 3];\n ring += height;\n // On the crest, signs turn to face the click.\n target += waveAlign * Math.min(1, height) * facingAngle(dx, dy);\n }\n\n // Ease towards the target the short way round, so a sign never spins more than 45°.\n const k = (j * cols + i) * 2;\n const remaining = wrap(target - data[k]);\n if (Math.abs(remaining) < settled) {\n data[k] = wrap(target);\n } else {\n data[k] = wrap(data[k] + remaining * ease);\n settling = true;\n }\n data[k + 1] = ring;\n }\n }\n changed = true;\n },\n };\n}\n"],"mappings":";;;AAKA,IAAM,UAAU,KAAK,KAAK;;AAE1B,IAAM,aAAa;;AAGnB,SAAS,KAAK,OAAuB;CACnC,OAAO,QAAQ,UAAU,KAAK,MAAM,QAAQ,OAAO;AACrD;AAgBA,SAAgB,kBAA6B;CAC3C,IAAI,uBAAO,IAAI,aAAa,CAAC;CAC7B,MAAM,2BAAW,IAAI,aAAA,EAA0B;;CAG/C,SAAS,UAAU,OAAwB,OAAe,UAAwB;EAChF,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,OAAO,MAAM;GACnB,SAAS,IAAI,KAAK,KAAK;GACvB,SAAS,IAAI,IAAI,KAAK,KAAK;GAC3B,SAAS,IAAI,IAAI,KAAK,QAAQ,KAAK;GACnC,SAAS,IAAI,IAAI,KAAK,aAAa,KAAK,KAAK,QAAQ;EACvD;CACF;CACA,IAAI,UAAU;CACd,IAAI,WAAW;CAEf,IAAI,QAAQ;CACZ,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,IAAI,cAAc;CAClB,IAAI,cAAc;CAClB,IAAI,cAA0C;CAC9C,IAAI,WAAW;CAEf,OAAO;EACL,IAAI,OAAO;GACT,OAAO;EACT;EACA,IAAI,UAAU;GACZ,OAAO;EACT;EACA,IAAI,WAAW;GACb,OAAO;EACT;EACA,OAAO,OAAO,SAAS,MAAM,OAAO;GAClC,MAAM,EAAE,MAAM,MAAM,SAAS,YAAY;GACzC,MAAM,EAAE,SAAS,WAAW,WAAW,cAAc,cAAc;GACnE,MAAM,UAAU,KAAK,WAAW,OAAO,OAAO;GAC9C,IAAI,SAAS,OAAO,IAAI,aAAa,OAAO,OAAO,CAAC;GAEpD,MAAM,QAAQ,QAAQ,eAAe,MAAM,QAAQ;GACnD,MAAM,EAAE,GAAG,IAAI,GAAG,OAAO,MAAM;GAG/B,MAAM,YACJ,CAAC,WACD,CAAC,YACD,CAAC,YACD,MAAM,WAAW,KACjB,UAAU,cACT,UAAU,KACR,OAAO,SAAS,OAAO,SAAS,YAAY,eAAe,YAAY,eAAe,YAAY;GACvG,WAAW,MAAM,SAAS;GAC1B,IAAI,WAAW;IACb,UAAU;IACV;GACF;GACA,QAAQ;GACR,QAAQ;GACR,YAAY;GACZ,cAAc;GACd,cAAc;GACd,cAAc;GAGd,MAAM,YAAY,MAAM;GACxB,UAAU,OAAO,WAAW,YAAY;GACxC,MAAM,YAAY,IAAI;GACtB,MAAM,UAAU,aAAa,QAAQ;GACrC,MAAM,OAAO,QAAQ,WAAW,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,KAAK,QAAQ,QAAQ,IAAI;GACjF,WAAW;GAEX,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK;IAC7B,MAAM,QAAQ,UAAU,IAAI;IAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,KAAK;KAC7B,MAAM,QAAQ,UAAU,IAAI;KAE5B,IAAI,SAAS;KAEb,IAAI,UAAU,GAAG,SAAS,QAAQ,YAAY,QAAQ,IAAI,QAAQ,EAAE;KAEpE,IAAI,OAAO;KACX,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,GAAG,KAAK,GAAG;MACzC,MAAM,KAAK,QAAQ,SAAS;MAC5B,MAAM,KAAK,QAAQ,SAAS,IAAI;MAChC,MAAM,QAAQ,SAAS,IAAI,KAAK;MAChC,IAAI,KAAK,IAAI,EAAE,IAAI,SAAS,KAAK,IAAI,EAAE,IAAI,OAAO;MAClD,MAAM,WAAW,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE;MAC5C,IAAI,KAAK,IAAI,WAAW,SAAS,IAAI,EAAE,IAAI,WAAW;MACtD,MAAM,SAAS,SAAS,UAAU,SAAS,IAAI,IAAI,SAAS,IAAI,SAAS,IAAI;MAC7E,QAAQ;MAER,UAAU,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,YAAY,IAAI,EAAE;KAChE;KAGA,MAAM,KAAK,IAAI,OAAO,KAAK;KAC3B,MAAM,YAAY,KAAK,SAAS,KAAK,EAAE;KACvC,IAAI,KAAK,IAAI,SAAS,IAAI,SACxB,KAAK,KAAK,KAAK,MAAM;UAChB;MACL,KAAK,KAAK,KAAK,KAAK,KAAK,YAAY,IAAI;MACzC,WAAW;KACb;KACA,KAAK,IAAI,KAAK;IAChB;GACF;GACA,UAAU;EACZ;CACF;AACF"}
@@ -0,0 +1,121 @@
1
+ const require_color = require("../../core/color.cjs");
2
+ const require_webgl = require("../../core/webgl.cjs");
3
+ const require_grid = require("../../shared/grid.cjs");
4
+ const require_grid_webgl = require("../../shared/grid-webgl.cjs");
5
+ const require_options = require("./options.cjs");
6
+ const require_turns = require("./turns.cjs");
7
+ // src/styles/plus/webgl.ts
8
+ var VERTEX = `#version 300 es
9
+ precision highp float;
10
+
11
+ layout(location = 0) in vec2 aTurn; // angle (radians), wave-crest height
12
+
13
+ ${require_grid_webgl.GRID_VERTEX_GLSL}
14
+ uniform float uSize; // arm length, CSS px
15
+ uniform float uWeight; // stroke weight, CSS px
16
+ uniform float uWaveWeight;
17
+
18
+ out vec2 vAxis; // cos, sin of the sign's angle
19
+ out float vArm; // device px
20
+ out float vHalfWeight; // device px
21
+ out float vPointSize; // device px
22
+
23
+ void main() {
24
+ vec2 ij = markCell();
25
+ vec2 p = restPosition(ij) + wander(ij);
26
+
27
+ vAxis = vec2(cos(aTurn.x), sin(aTurn.x));
28
+ vArm = uSize * uDpr;
29
+ vHalfWeight = 0.5 * uWeight * (1.0 + uWaveWeight * min(aTurn.y, 2.0)) * uDpr;
30
+ vPointSize = 2.0 * (vArm + vHalfWeight) + 2.0; // room for the antialiased edge
31
+ gl_PointSize = vPointSize;
32
+ gl_Position = toClip(p);
33
+ }`;
34
+ var FRAGMENT = `#version 300 es
35
+ precision highp float;
36
+
37
+ uniform vec4 uColor; // straight RGBA, opacity applied
38
+ in vec2 vAxis;
39
+ in float vArm;
40
+ in float vHalfWeight;
41
+ in float vPointSize;
42
+ out vec4 outColor;
43
+
44
+ // Distance to a round-capped arm of half-length vArm along x.
45
+ float arm(vec2 q) {
46
+ return length(vec2(max(abs(q.x) - vArm, 0.0), q.y)) - vHalfWeight;
47
+ }
48
+
49
+ void main() {
50
+ vec2 p = (gl_PointCoord - 0.5) * vPointSize;
51
+ vec2 q = vec2(dot(p, vAxis), dot(p, vec2(-vAxis.y, vAxis.x))); // into the sign's frame
52
+ float d = min(arm(q), arm(q.yx));
53
+ float a = uColor.a * clamp(0.5 - d, 0.0, 1.0);
54
+ outColor = vec4(uColor.rgb * a, a); // premultiplied; the engine blends it over the background
55
+ }`;
56
+ var UNIFORMS = [
57
+ ...require_grid_webgl.GRID_UNIFORMS,
58
+ "uSize",
59
+ "uWeight",
60
+ "uWaveWeight",
61
+ "uColor"
62
+ ];
63
+ /**
64
+ * Every sign is one GL point: the vertex shader places it and the fragment
65
+ * shader draws the cross. Angles are eased on the CPU (SignTurns) and uploaded
66
+ * only on frames where they changed.
67
+ */
68
+ function createPlusWebGL(gl, initial) {
69
+ let options = require_options.resolvePlusOptions(initial);
70
+ const waves = require_grid.createWaveList();
71
+ const turns = require_turns.createSignTurns();
72
+ let clock = 0;
73
+ let uploaded = -1;
74
+ const program = require_webgl.createProgram(gl, VERTEX, FRAGMENT);
75
+ const uniforms = program ? require_webgl.getUniforms(gl, program, UNIFORMS) : null;
76
+ const vao = gl.createVertexArray();
77
+ const buffer = gl.createBuffer();
78
+ gl.bindVertexArray(vao);
79
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
80
+ gl.enableVertexAttribArray(0);
81
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
82
+ gl.bindVertexArray(null);
83
+ return {
84
+ update(next) {
85
+ options = require_options.resolvePlusOptions(next);
86
+ },
87
+ render(frame) {
88
+ if (!frame.reducedMotion) clock += frame.dt * options.jitterSpeed * require_grid.TAU;
89
+ waves.advance(frame, options.waveDuration, require_options.wavesEnabled(options));
90
+ const grid = require_grid.gridLayout(frame.width, frame.height, options.spacing);
91
+ turns.update(frame, options, grid, waves.waves);
92
+ const busy = waves.waves.length > 0 || turns.settling;
93
+ if (!program || !uniforms) return busy;
94
+ const count = grid.cols * grid.rows;
95
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
96
+ if (uploaded !== count) {
97
+ gl.bufferData(gl.ARRAY_BUFFER, turns.data, gl.DYNAMIC_DRAW);
98
+ uploaded = count;
99
+ } else if (turns.changed) gl.bufferSubData(gl.ARRAY_BUFFER, 0, turns.data);
100
+ const color = require_color.parseColor(options.color);
101
+ gl.useProgram(program);
102
+ require_grid_webgl.setGridUniforms(gl, uniforms, grid, frame, options.spacing, clock, options.jitter);
103
+ gl.uniform1f(uniforms.uSize, options.size);
104
+ gl.uniform1f(uniforms.uWeight, options.weight);
105
+ gl.uniform1f(uniforms.uWaveWeight, options.waveWeight);
106
+ gl.uniform4f(uniforms.uColor, color[0], color[1], color[2], color[3] * options.opacity);
107
+ gl.bindVertexArray(vao);
108
+ gl.drawArrays(gl.POINTS, 0, count);
109
+ gl.bindVertexArray(null);
110
+ return busy;
111
+ },
112
+ destroy() {
113
+ gl.deleteProgram(program);
114
+ gl.deleteBuffer(buffer);
115
+ gl.deleteVertexArray(vao);
116
+ }
117
+ };
118
+ }
119
+ exports.createPlusWebGL = createPlusWebGL;
120
+
121
+ //# sourceMappingURL=webgl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgl.cjs","names":["GRID_VERTEX_GLSL","GRID_UNIFORMS","resolvePlusOptions","createWaveList","createSignTurns","createProgram","getUniforms","TAU","wavesEnabled","gridLayout","parseColor"],"sources":["../../../src/styles/plus/webgl.ts"],"sourcesContent":["import { parseColor } from '../../core/color';\nimport type { StyleRenderer } from '../../core/style';\nimport { createProgram, getUniforms } from '../../core/webgl';\nimport { createWaveList, gridLayout, TAU } from '../../shared/grid';\nimport { GRID_UNIFORMS, GRID_VERTEX_GLSL, setGridUniforms } from '../../shared/grid-webgl';\nimport { resolvePlusOptions, wavesEnabled, type PlusOptions } from './options';\nimport { createSignTurns } from './turns';\n\n// Wander mirrors canvas2d.ts; each sign's angle and wave height come from SignTurns (aTurn).\nconst VERTEX = `#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec2 aTurn; // angle (radians), wave-crest height\n\n${GRID_VERTEX_GLSL}\nuniform float uSize; // arm length, CSS px\nuniform float uWeight; // stroke weight, CSS px\nuniform float uWaveWeight;\n\nout vec2 vAxis; // cos, sin of the sign's angle\nout float vArm; // device px\nout float vHalfWeight; // device px\nout float vPointSize; // device px\n\nvoid main() {\n vec2 ij = markCell();\n vec2 p = restPosition(ij) + wander(ij);\n\n vAxis = vec2(cos(aTurn.x), sin(aTurn.x));\n vArm = uSize * uDpr;\n vHalfWeight = 0.5 * uWeight * (1.0 + uWaveWeight * min(aTurn.y, 2.0)) * uDpr;\n vPointSize = 2.0 * (vArm + vHalfWeight) + 2.0; // room for the antialiased edge\n gl_PointSize = vPointSize;\n gl_Position = toClip(p);\n}`;\n\nconst FRAGMENT = `#version 300 es\nprecision highp float;\n\nuniform vec4 uColor; // straight RGBA, opacity applied\nin vec2 vAxis;\nin float vArm;\nin float vHalfWeight;\nin float vPointSize;\nout vec4 outColor;\n\n// Distance to a round-capped arm of half-length vArm along x.\nfloat arm(vec2 q) {\n return length(vec2(max(abs(q.x) - vArm, 0.0), q.y)) - vHalfWeight;\n}\n\nvoid main() {\n vec2 p = (gl_PointCoord - 0.5) * vPointSize;\n vec2 q = vec2(dot(p, vAxis), dot(p, vec2(-vAxis.y, vAxis.x))); // into the sign's frame\n float d = min(arm(q), arm(q.yx));\n float a = uColor.a * clamp(0.5 - d, 0.0, 1.0);\n outColor = vec4(uColor.rgb * a, a); // premultiplied; the engine blends it over the background\n}`;\n\nconst UNIFORMS = [...GRID_UNIFORMS, 'uSize', 'uWeight', 'uWaveWeight', 'uColor'] as const;\n\n/**\n * Every sign is one GL point: the vertex shader places it and the fragment\n * shader draws the cross. Angles are eased on the CPU (SignTurns) and uploaded\n * only on frames where they changed.\n */\nexport function createPlusWebGL(gl: WebGL2RenderingContext, initial: PlusOptions): StyleRenderer<PlusOptions> {\n let options = resolvePlusOptions(initial);\n const waves = createWaveList();\n const turns = createSignTurns();\n let clock = 0;\n let uploaded = -1; // sign count the buffer was last sized for\n\n const program = createProgram(gl, VERTEX, FRAGMENT);\n const uniforms = program ? getUniforms(gl, program, UNIFORMS) : null;\n const vao = gl.createVertexArray();\n const buffer = gl.createBuffer();\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n\n return {\n update(next) {\n options = resolvePlusOptions(next);\n },\n render(frame) {\n if (!frame.reducedMotion) clock += frame.dt * options.jitterSpeed * TAU;\n waves.advance(frame, options.waveDuration, wavesEnabled(options));\n const grid = gridLayout(frame.width, frame.height, options.spacing);\n turns.update(frame, options, grid, waves.waves);\n const busy = waves.waves.length > 0 || turns.settling;\n if (!program || !uniforms) return busy; // context lost during setup\n\n const count = grid.cols * grid.rows;\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n if (uploaded !== count) {\n gl.bufferData(gl.ARRAY_BUFFER, turns.data, gl.DYNAMIC_DRAW);\n uploaded = count;\n } else if (turns.changed) {\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, turns.data);\n }\n\n const color = parseColor(options.color);\n gl.useProgram(program);\n setGridUniforms(gl, uniforms, grid, frame, options.spacing, clock, options.jitter);\n gl.uniform1f(uniforms.uSize, options.size);\n gl.uniform1f(uniforms.uWeight, options.weight);\n gl.uniform1f(uniforms.uWaveWeight, options.waveWeight);\n gl.uniform4f(uniforms.uColor, color[0], color[1], color[2], color[3] * options.opacity);\n gl.bindVertexArray(vao);\n gl.drawArrays(gl.POINTS, 0, count);\n gl.bindVertexArray(null);\n return busy;\n },\n destroy() {\n gl.deleteProgram(program);\n gl.deleteBuffer(buffer);\n gl.deleteVertexArray(vao);\n },\n };\n}\n"],"mappings":";;;;;;;AASA,IAAM,SAAS;;;;;EAKbA,mBAAAA,iBAAiB;;;;;;;;;;;;;;;;;;;;;AAsBnB,IAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;AAuBjB,IAAM,WAAW;CAAC,GAAGC,mBAAAA;CAAe;CAAS;CAAW;CAAe;AAAQ;;;;;;AAO/E,SAAgB,gBAAgB,IAA4B,SAAkD;CAC5G,IAAI,UAAUC,gBAAAA,mBAAmB,OAAO;CACxC,MAAM,QAAQC,aAAAA,eAAe;CAC7B,MAAM,QAAQC,cAAAA,gBAAgB;CAC9B,IAAI,QAAQ;CACZ,IAAI,WAAW;CAEf,MAAM,UAAUC,cAAAA,cAAc,IAAI,QAAQ,QAAQ;CAClD,MAAM,WAAW,UAAUC,cAAAA,YAAY,IAAI,SAAS,QAAQ,IAAI;CAChE,MAAM,MAAM,GAAG,kBAAkB;CACjC,MAAM,SAAS,GAAG,aAAa;CAC/B,GAAG,gBAAgB,GAAG;CACtB,GAAG,WAAW,GAAG,cAAc,MAAM;CACrC,GAAG,wBAAwB,CAAC;CAC5B,GAAG,oBAAoB,GAAG,GAAG,GAAG,OAAO,OAAO,GAAG,CAAC;CAClD,GAAG,gBAAgB,IAAI;CAEvB,OAAO;EACL,OAAO,MAAM;GACX,UAAUJ,gBAAAA,mBAAmB,IAAI;EACnC;EACA,OAAO,OAAO;GACZ,IAAI,CAAC,MAAM,eAAe,SAAS,MAAM,KAAK,QAAQ,cAAcK,aAAAA;GACpE,MAAM,QAAQ,OAAO,QAAQ,cAAcC,gBAAAA,aAAa,OAAO,CAAC;GAChE,MAAM,OAAOC,aAAAA,WAAW,MAAM,OAAO,MAAM,QAAQ,QAAQ,OAAO;GAClE,MAAM,OAAO,OAAO,SAAS,MAAM,MAAM,KAAK;GAC9C,MAAM,OAAO,MAAM,MAAM,SAAS,KAAK,MAAM;GAC7C,IAAI,CAAC,WAAW,CAAC,UAAU,OAAO;GAElC,MAAM,QAAQ,KAAK,OAAO,KAAK;GAC/B,GAAG,WAAW,GAAG,cAAc,MAAM;GACrC,IAAI,aAAa,OAAO;IACtB,GAAG,WAAW,GAAG,cAAc,MAAM,MAAM,GAAG,YAAY;IAC1D,WAAW;GACb,OAAO,IAAI,MAAM,SACf,GAAG,cAAc,GAAG,cAAc,GAAG,MAAM,IAAI;GAGjD,MAAM,QAAQC,cAAAA,WAAW,QAAQ,KAAK;GACtC,GAAG,WAAW,OAAO;GACrB,mBAAA,gBAAgB,IAAI,UAAU,MAAM,OAAO,QAAQ,SAAS,OAAO,QAAQ,MAAM;GACjF,GAAG,UAAU,SAAS,OAAO,QAAQ,IAAI;GACzC,GAAG,UAAU,SAAS,SAAS,QAAQ,MAAM;GAC7C,GAAG,UAAU,SAAS,aAAa,QAAQ,UAAU;GACrD,GAAG,UAAU,SAAS,QAAQ,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,KAAK,QAAQ,OAAO;GACtF,GAAG,gBAAgB,GAAG;GACtB,GAAG,WAAW,GAAG,QAAQ,GAAG,KAAK;GACjC,GAAG,gBAAgB,IAAI;GACvB,OAAO;EACT;EACA,UAAU;GACR,GAAG,cAAc,OAAO;GACxB,GAAG,aAAa,MAAM;GACtB,GAAG,kBAAkB,GAAG;EAC1B;CACF;AACF"}
@@ -0,0 +1,8 @@
1
+ import type { StyleRenderer } from '../../core/style.cjs';
2
+ import { type PlusOptions } from './options.cjs';
3
+ /**
4
+ * Every sign is one GL point: the vertex shader places it and the fragment
5
+ * shader draws the cross. Angles are eased on the CPU (SignTurns) and uploaded
6
+ * only on frames where they changed.
7
+ */
8
+ export declare function createPlusWebGL(gl: WebGL2RenderingContext, initial: PlusOptions): StyleRenderer<PlusOptions>;
@@ -0,0 +1,8 @@
1
+ import type { StyleRenderer } from '../../core/style.js';
2
+ import { type PlusOptions } from './options.js';
3
+ /**
4
+ * Every sign is one GL point: the vertex shader places it and the fragment
5
+ * shader draws the cross. Angles are eased on the CPU (SignTurns) and uploaded
6
+ * only on frames where they changed.
7
+ */
8
+ export declare function createPlusWebGL(gl: WebGL2RenderingContext, initial: PlusOptions): StyleRenderer<PlusOptions>;
@@ -0,0 +1,121 @@
1
+ import { parseColor } from "../../core/color.js";
2
+ import { createProgram, getUniforms } from "../../core/webgl.js";
3
+ import { TAU, createWaveList, gridLayout } from "../../shared/grid.js";
4
+ import { GRID_UNIFORMS, GRID_VERTEX_GLSL, setGridUniforms } from "../../shared/grid-webgl.js";
5
+ import { resolvePlusOptions, wavesEnabled } from "./options.js";
6
+ import { createSignTurns } from "./turns.js";
7
+ // src/styles/plus/webgl.ts
8
+ var VERTEX = `#version 300 es
9
+ precision highp float;
10
+
11
+ layout(location = 0) in vec2 aTurn; // angle (radians), wave-crest height
12
+
13
+ ${GRID_VERTEX_GLSL}
14
+ uniform float uSize; // arm length, CSS px
15
+ uniform float uWeight; // stroke weight, CSS px
16
+ uniform float uWaveWeight;
17
+
18
+ out vec2 vAxis; // cos, sin of the sign's angle
19
+ out float vArm; // device px
20
+ out float vHalfWeight; // device px
21
+ out float vPointSize; // device px
22
+
23
+ void main() {
24
+ vec2 ij = markCell();
25
+ vec2 p = restPosition(ij) + wander(ij);
26
+
27
+ vAxis = vec2(cos(aTurn.x), sin(aTurn.x));
28
+ vArm = uSize * uDpr;
29
+ vHalfWeight = 0.5 * uWeight * (1.0 + uWaveWeight * min(aTurn.y, 2.0)) * uDpr;
30
+ vPointSize = 2.0 * (vArm + vHalfWeight) + 2.0; // room for the antialiased edge
31
+ gl_PointSize = vPointSize;
32
+ gl_Position = toClip(p);
33
+ }`;
34
+ var FRAGMENT = `#version 300 es
35
+ precision highp float;
36
+
37
+ uniform vec4 uColor; // straight RGBA, opacity applied
38
+ in vec2 vAxis;
39
+ in float vArm;
40
+ in float vHalfWeight;
41
+ in float vPointSize;
42
+ out vec4 outColor;
43
+
44
+ // Distance to a round-capped arm of half-length vArm along x.
45
+ float arm(vec2 q) {
46
+ return length(vec2(max(abs(q.x) - vArm, 0.0), q.y)) - vHalfWeight;
47
+ }
48
+
49
+ void main() {
50
+ vec2 p = (gl_PointCoord - 0.5) * vPointSize;
51
+ vec2 q = vec2(dot(p, vAxis), dot(p, vec2(-vAxis.y, vAxis.x))); // into the sign's frame
52
+ float d = min(arm(q), arm(q.yx));
53
+ float a = uColor.a * clamp(0.5 - d, 0.0, 1.0);
54
+ outColor = vec4(uColor.rgb * a, a); // premultiplied; the engine blends it over the background
55
+ }`;
56
+ var UNIFORMS = [
57
+ ...GRID_UNIFORMS,
58
+ "uSize",
59
+ "uWeight",
60
+ "uWaveWeight",
61
+ "uColor"
62
+ ];
63
+ /**
64
+ * Every sign is one GL point: the vertex shader places it and the fragment
65
+ * shader draws the cross. Angles are eased on the CPU (SignTurns) and uploaded
66
+ * only on frames where they changed.
67
+ */
68
+ function createPlusWebGL(gl, initial) {
69
+ let options = resolvePlusOptions(initial);
70
+ const waves = createWaveList();
71
+ const turns = createSignTurns();
72
+ let clock = 0;
73
+ let uploaded = -1;
74
+ const program = createProgram(gl, VERTEX, FRAGMENT);
75
+ const uniforms = program ? getUniforms(gl, program, UNIFORMS) : null;
76
+ const vao = gl.createVertexArray();
77
+ const buffer = gl.createBuffer();
78
+ gl.bindVertexArray(vao);
79
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
80
+ gl.enableVertexAttribArray(0);
81
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
82
+ gl.bindVertexArray(null);
83
+ return {
84
+ update(next) {
85
+ options = resolvePlusOptions(next);
86
+ },
87
+ render(frame) {
88
+ if (!frame.reducedMotion) clock += frame.dt * options.jitterSpeed * TAU;
89
+ waves.advance(frame, options.waveDuration, wavesEnabled(options));
90
+ const grid = gridLayout(frame.width, frame.height, options.spacing);
91
+ turns.update(frame, options, grid, waves.waves);
92
+ const busy = waves.waves.length > 0 || turns.settling;
93
+ if (!program || !uniforms) return busy;
94
+ const count = grid.cols * grid.rows;
95
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
96
+ if (uploaded !== count) {
97
+ gl.bufferData(gl.ARRAY_BUFFER, turns.data, gl.DYNAMIC_DRAW);
98
+ uploaded = count;
99
+ } else if (turns.changed) gl.bufferSubData(gl.ARRAY_BUFFER, 0, turns.data);
100
+ const color = parseColor(options.color);
101
+ gl.useProgram(program);
102
+ setGridUniforms(gl, uniforms, grid, frame, options.spacing, clock, options.jitter);
103
+ gl.uniform1f(uniforms.uSize, options.size);
104
+ gl.uniform1f(uniforms.uWeight, options.weight);
105
+ gl.uniform1f(uniforms.uWaveWeight, options.waveWeight);
106
+ gl.uniform4f(uniforms.uColor, color[0], color[1], color[2], color[3] * options.opacity);
107
+ gl.bindVertexArray(vao);
108
+ gl.drawArrays(gl.POINTS, 0, count);
109
+ gl.bindVertexArray(null);
110
+ return busy;
111
+ },
112
+ destroy() {
113
+ gl.deleteProgram(program);
114
+ gl.deleteBuffer(buffer);
115
+ gl.deleteVertexArray(vao);
116
+ }
117
+ };
118
+ }
119
+ export { createPlusWebGL };
120
+
121
+ //# sourceMappingURL=webgl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgl.js","names":[],"sources":["../../../src/styles/plus/webgl.ts"],"sourcesContent":["import { parseColor } from '../../core/color';\nimport type { StyleRenderer } from '../../core/style';\nimport { createProgram, getUniforms } from '../../core/webgl';\nimport { createWaveList, gridLayout, TAU } from '../../shared/grid';\nimport { GRID_UNIFORMS, GRID_VERTEX_GLSL, setGridUniforms } from '../../shared/grid-webgl';\nimport { resolvePlusOptions, wavesEnabled, type PlusOptions } from './options';\nimport { createSignTurns } from './turns';\n\n// Wander mirrors canvas2d.ts; each sign's angle and wave height come from SignTurns (aTurn).\nconst VERTEX = `#version 300 es\nprecision highp float;\n\nlayout(location = 0) in vec2 aTurn; // angle (radians), wave-crest height\n\n${GRID_VERTEX_GLSL}\nuniform float uSize; // arm length, CSS px\nuniform float uWeight; // stroke weight, CSS px\nuniform float uWaveWeight;\n\nout vec2 vAxis; // cos, sin of the sign's angle\nout float vArm; // device px\nout float vHalfWeight; // device px\nout float vPointSize; // device px\n\nvoid main() {\n vec2 ij = markCell();\n vec2 p = restPosition(ij) + wander(ij);\n\n vAxis = vec2(cos(aTurn.x), sin(aTurn.x));\n vArm = uSize * uDpr;\n vHalfWeight = 0.5 * uWeight * (1.0 + uWaveWeight * min(aTurn.y, 2.0)) * uDpr;\n vPointSize = 2.0 * (vArm + vHalfWeight) + 2.0; // room for the antialiased edge\n gl_PointSize = vPointSize;\n gl_Position = toClip(p);\n}`;\n\nconst FRAGMENT = `#version 300 es\nprecision highp float;\n\nuniform vec4 uColor; // straight RGBA, opacity applied\nin vec2 vAxis;\nin float vArm;\nin float vHalfWeight;\nin float vPointSize;\nout vec4 outColor;\n\n// Distance to a round-capped arm of half-length vArm along x.\nfloat arm(vec2 q) {\n return length(vec2(max(abs(q.x) - vArm, 0.0), q.y)) - vHalfWeight;\n}\n\nvoid main() {\n vec2 p = (gl_PointCoord - 0.5) * vPointSize;\n vec2 q = vec2(dot(p, vAxis), dot(p, vec2(-vAxis.y, vAxis.x))); // into the sign's frame\n float d = min(arm(q), arm(q.yx));\n float a = uColor.a * clamp(0.5 - d, 0.0, 1.0);\n outColor = vec4(uColor.rgb * a, a); // premultiplied; the engine blends it over the background\n}`;\n\nconst UNIFORMS = [...GRID_UNIFORMS, 'uSize', 'uWeight', 'uWaveWeight', 'uColor'] as const;\n\n/**\n * Every sign is one GL point: the vertex shader places it and the fragment\n * shader draws the cross. Angles are eased on the CPU (SignTurns) and uploaded\n * only on frames where they changed.\n */\nexport function createPlusWebGL(gl: WebGL2RenderingContext, initial: PlusOptions): StyleRenderer<PlusOptions> {\n let options = resolvePlusOptions(initial);\n const waves = createWaveList();\n const turns = createSignTurns();\n let clock = 0;\n let uploaded = -1; // sign count the buffer was last sized for\n\n const program = createProgram(gl, VERTEX, FRAGMENT);\n const uniforms = program ? getUniforms(gl, program, UNIFORMS) : null;\n const vao = gl.createVertexArray();\n const buffer = gl.createBuffer();\n gl.bindVertexArray(vao);\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.enableVertexAttribArray(0);\n gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);\n gl.bindVertexArray(null);\n\n return {\n update(next) {\n options = resolvePlusOptions(next);\n },\n render(frame) {\n if (!frame.reducedMotion) clock += frame.dt * options.jitterSpeed * TAU;\n waves.advance(frame, options.waveDuration, wavesEnabled(options));\n const grid = gridLayout(frame.width, frame.height, options.spacing);\n turns.update(frame, options, grid, waves.waves);\n const busy = waves.waves.length > 0 || turns.settling;\n if (!program || !uniforms) return busy; // context lost during setup\n\n const count = grid.cols * grid.rows;\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n if (uploaded !== count) {\n gl.bufferData(gl.ARRAY_BUFFER, turns.data, gl.DYNAMIC_DRAW);\n uploaded = count;\n } else if (turns.changed) {\n gl.bufferSubData(gl.ARRAY_BUFFER, 0, turns.data);\n }\n\n const color = parseColor(options.color);\n gl.useProgram(program);\n setGridUniforms(gl, uniforms, grid, frame, options.spacing, clock, options.jitter);\n gl.uniform1f(uniforms.uSize, options.size);\n gl.uniform1f(uniforms.uWeight, options.weight);\n gl.uniform1f(uniforms.uWaveWeight, options.waveWeight);\n gl.uniform4f(uniforms.uColor, color[0], color[1], color[2], color[3] * options.opacity);\n gl.bindVertexArray(vao);\n gl.drawArrays(gl.POINTS, 0, count);\n gl.bindVertexArray(null);\n return busy;\n },\n destroy() {\n gl.deleteProgram(program);\n gl.deleteBuffer(buffer);\n gl.deleteVertexArray(vao);\n },\n };\n}\n"],"mappings":";;;;;;;AASA,IAAM,SAAS;;;;;EAKb,iBAAiB;;;;;;;;;;;;;;;;;;;;;AAsBnB,IAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;AAuBjB,IAAM,WAAW;CAAC,GAAG;CAAe;CAAS;CAAW;CAAe;AAAQ;;;;;;AAO/E,SAAgB,gBAAgB,IAA4B,SAAkD;CAC5G,IAAI,UAAU,mBAAmB,OAAO;CACxC,MAAM,QAAQ,eAAe;CAC7B,MAAM,QAAQ,gBAAgB;CAC9B,IAAI,QAAQ;CACZ,IAAI,WAAW;CAEf,MAAM,UAAU,cAAc,IAAI,QAAQ,QAAQ;CAClD,MAAM,WAAW,UAAU,YAAY,IAAI,SAAS,QAAQ,IAAI;CAChE,MAAM,MAAM,GAAG,kBAAkB;CACjC,MAAM,SAAS,GAAG,aAAa;CAC/B,GAAG,gBAAgB,GAAG;CACtB,GAAG,WAAW,GAAG,cAAc,MAAM;CACrC,GAAG,wBAAwB,CAAC;CAC5B,GAAG,oBAAoB,GAAG,GAAG,GAAG,OAAO,OAAO,GAAG,CAAC;CAClD,GAAG,gBAAgB,IAAI;CAEvB,OAAO;EACL,OAAO,MAAM;GACX,UAAU,mBAAmB,IAAI;EACnC;EACA,OAAO,OAAO;GACZ,IAAI,CAAC,MAAM,eAAe,SAAS,MAAM,KAAK,QAAQ,cAAc;GACpE,MAAM,QAAQ,OAAO,QAAQ,cAAc,aAAa,OAAO,CAAC;GAChE,MAAM,OAAO,WAAW,MAAM,OAAO,MAAM,QAAQ,QAAQ,OAAO;GAClE,MAAM,OAAO,OAAO,SAAS,MAAM,MAAM,KAAK;GAC9C,MAAM,OAAO,MAAM,MAAM,SAAS,KAAK,MAAM;GAC7C,IAAI,CAAC,WAAW,CAAC,UAAU,OAAO;GAElC,MAAM,QAAQ,KAAK,OAAO,KAAK;GAC/B,GAAG,WAAW,GAAG,cAAc,MAAM;GACrC,IAAI,aAAa,OAAO;IACtB,GAAG,WAAW,GAAG,cAAc,MAAM,MAAM,GAAG,YAAY;IAC1D,WAAW;GACb,OAAO,IAAI,MAAM,SACf,GAAG,cAAc,GAAG,cAAc,GAAG,MAAM,IAAI;GAGjD,MAAM,QAAQ,WAAW,QAAQ,KAAK;GACtC,GAAG,WAAW,OAAO;GACrB,gBAAgB,IAAI,UAAU,MAAM,OAAO,QAAQ,SAAS,OAAO,QAAQ,MAAM;GACjF,GAAG,UAAU,SAAS,OAAO,QAAQ,IAAI;GACzC,GAAG,UAAU,SAAS,SAAS,QAAQ,MAAM;GAC7C,GAAG,UAAU,SAAS,aAAa,QAAQ,UAAU;GACrD,GAAG,UAAU,SAAS,QAAQ,MAAM,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,KAAK,QAAQ,OAAO;GACtF,GAAG,gBAAgB,GAAG;GACtB,GAAG,WAAW,GAAG,QAAQ,GAAG,KAAK;GACjC,GAAG,gBAAgB,IAAI;GACvB,OAAO;EACT;EACA,UAAU;GACR,GAAG,cAAc,OAAO;GACxB,GAAG,aAAa,MAAM;GACtB,GAAG,kBAAkB,GAAG;EAC1B;CACF;AACF"}
@@ -0,0 +1,77 @@
1
+ const require_buffer = require("../../shared/buffer.cjs");
2
+ const require_contours = require("./contours.cjs");
3
+ const require_options = require("./options.cjs");
4
+ const require_terrain = require("./terrain.cjs");
5
+ // src/styles/topo/canvas2d.ts
6
+ /** Marching squares on the CPU, stroked with the Canvas 2D API. */
7
+ function createTopoCanvas2D(ctx, initial) {
8
+ let options = require_options.resolveTopoOptions(initial);
9
+ const terrain = require_terrain.createTerrain(options);
10
+ let field = /* @__PURE__ */ new Float32Array(0);
11
+ const minor = require_buffer.createFloatBuffer(4096);
12
+ const major = require_buffer.createFloatBuffer(1024);
13
+ function collectSegment(x1, y1, x2, y2, level) {
14
+ const buffer = options.majorEvery > 0 && level % options.majorEvery === 0 ? major : minor;
15
+ const data = require_buffer.reserve(buffer, 4);
16
+ data[buffer.length++] = x1;
17
+ data[buffer.length++] = y1;
18
+ data[buffer.length++] = x2;
19
+ data[buffer.length++] = y2;
20
+ }
21
+ function stroke(buffer, color, lineWidth) {
22
+ const { data, length } = buffer;
23
+ ctx.beginPath();
24
+ for (let k = 0; k < length; k += 4) {
25
+ ctx.moveTo(data[k], data[k + 1]);
26
+ ctx.lineTo(data[k + 2], data[k + 3]);
27
+ }
28
+ ctx.strokeStyle = color;
29
+ ctx.lineWidth = lineWidth;
30
+ ctx.stroke();
31
+ }
32
+ /** Blend the keyframes and add the pointer bump into `field`. */
33
+ function sampleField(blend, pointerX, pointerY, bump) {
34
+ const { cols, rows, keyA, keyB } = terrain.field;
35
+ const size = cols * rows;
36
+ if (field.length !== size) field = new Float32Array(size);
37
+ for (let idx = 0; idx < size; idx++) {
38
+ const a = keyA[idx];
39
+ field[idx] = a + (keyB[idx] - a) * blend;
40
+ }
41
+ if (bump === 0) return;
42
+ const { cellSize, pointerRadius } = options;
43
+ const radius2 = pointerRadius * pointerRadius;
44
+ const reach = 3 * pointerRadius;
45
+ const i0 = Math.max(0, Math.floor((pointerX - reach) / cellSize));
46
+ const i1 = Math.min(cols - 1, Math.ceil((pointerX + reach) / cellSize));
47
+ const j0 = Math.max(0, Math.floor((pointerY - reach) / cellSize));
48
+ const j1 = Math.min(rows - 1, Math.ceil((pointerY + reach) / cellSize));
49
+ for (let j = j0; j <= j1; j++) {
50
+ const dy = j * cellSize - pointerY;
51
+ for (let i = i0; i <= i1; i++) {
52
+ const dx = i * cellSize - pointerX;
53
+ field[j * cols + i] += bump * Math.exp(-(dx * dx + dy * dy) / radius2);
54
+ }
55
+ }
56
+ }
57
+ return {
58
+ update(next) {
59
+ options = require_options.resolveTopoOptions(next);
60
+ terrain.configure(options);
61
+ },
62
+ render(frame) {
63
+ const blend = terrain.advance(frame);
64
+ const { pointer } = frame;
65
+ sampleField(blend, pointer.x, pointer.y, options.pointerStrength * pointer.influence);
66
+ minor.length = major.length = 0;
67
+ const { cols, rows } = terrain.field;
68
+ require_contours.traceContours(field, cols, rows, options.cellSize, options.levelStep, collectSegment);
69
+ ctx.globalAlpha = options.lineOpacity;
70
+ stroke(minor, options.lineColor, options.lineWidth);
71
+ if (options.majorEvery > 0) stroke(major, options.majorLineColor, options.majorLineWidth);
72
+ }
73
+ };
74
+ }
75
+ exports.createTopoCanvas2D = createTopoCanvas2D;
76
+
77
+ //# sourceMappingURL=canvas2d.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas2d.cjs","names":["resolveTopoOptions","createTerrain","createFloatBuffer","reserve"],"sources":["../../../src/styles/topo/canvas2d.ts"],"sourcesContent":["import type { StyleRenderer } from '../../core/style';\nimport { createFloatBuffer, reserve, type FloatBuffer } from '../../shared/buffer';\nimport { traceContours } from './contours';\nimport { resolveTopoOptions, type TopoOptions } from './options';\nimport { createTerrain } from './terrain';\n\n/** Marching squares on the CPU, stroked with the Canvas 2D API. */\nexport function createTopoCanvas2D(ctx: CanvasRenderingContext2D, initial: TopoOptions): StyleRenderer<TopoOptions> {\n let options = resolveTopoOptions(initial);\n const terrain = createTerrain(options);\n let field = new Float32Array(0);\n\n // Segments are collected into reusable buffers and stroked from the context's\n // own path, so a frame allocates nothing. Path2D objects per frame pile up as\n // native garbage faster than the browser frees them.\n // Line segments as x1, y1, x2, y2.\n const minor = createFloatBuffer(4096);\n const major = createFloatBuffer(1024);\n\n function collectSegment(x1: number, y1: number, x2: number, y2: number, level: number): void {\n const buffer = options.majorEvery > 0 && level % options.majorEvery === 0 ? major : minor;\n const data = reserve(buffer, 4);\n data[buffer.length++] = x1;\n data[buffer.length++] = y1;\n data[buffer.length++] = x2;\n data[buffer.length++] = y2;\n }\n\n function stroke(buffer: FloatBuffer, color: string, lineWidth: number): void {\n const { data, length } = buffer;\n ctx.beginPath();\n for (let k = 0; k < length; k += 4) {\n ctx.moveTo(data[k], data[k + 1]);\n ctx.lineTo(data[k + 2], data[k + 3]);\n }\n ctx.strokeStyle = color;\n ctx.lineWidth = lineWidth;\n ctx.stroke();\n }\n\n /** Blend the keyframes and add the pointer bump into `field`. */\n function sampleField(blend: number, pointerX: number, pointerY: number, bump: number): void {\n const { cols, rows, keyA, keyB } = terrain.field;\n const size = cols * rows;\n if (field.length !== size) field = new Float32Array(size);\n for (let idx = 0; idx < size; idx++) {\n const a = keyA[idx];\n field[idx] = a + (keyB[idx] - a) * blend;\n }\n\n if (bump === 0) return;\n const { cellSize, pointerRadius } = options;\n const radius2 = pointerRadius * pointerRadius;\n // Beyond 3 radii the bump adds < 0.0001, so only touch the cells around the pointer.\n const reach = 3 * pointerRadius;\n const i0 = Math.max(0, Math.floor((pointerX - reach) / cellSize));\n const i1 = Math.min(cols - 1, Math.ceil((pointerX + reach) / cellSize));\n const j0 = Math.max(0, Math.floor((pointerY - reach) / cellSize));\n const j1 = Math.min(rows - 1, Math.ceil((pointerY + reach) / cellSize));\n for (let j = j0; j <= j1; j++) {\n const dy = j * cellSize - pointerY;\n for (let i = i0; i <= i1; i++) {\n const dx = i * cellSize - pointerX;\n field[j * cols + i] += bump * Math.exp(-(dx * dx + dy * dy) / radius2);\n }\n }\n }\n\n return {\n update(next) {\n options = resolveTopoOptions(next);\n terrain.configure(options);\n },\n render(frame) {\n const blend = terrain.advance(frame);\n const { pointer } = frame;\n sampleField(blend, pointer.x, pointer.y, options.pointerStrength * pointer.influence);\n\n minor.length = major.length = 0;\n const { cols, rows } = terrain.field;\n traceContours(field, cols, rows, options.cellSize, options.levelStep, collectSegment);\n\n ctx.globalAlpha = options.lineOpacity;\n stroke(minor, options.lineColor, options.lineWidth);\n if (options.majorEvery > 0) stroke(major, options.majorLineColor, options.majorLineWidth);\n },\n };\n}\n"],"mappings":";;;;;;AAOA,SAAgB,mBAAmB,KAA+B,SAAkD;CAClH,IAAI,UAAUA,gBAAAA,mBAAmB,OAAO;CACxC,MAAM,UAAUC,gBAAAA,cAAc,OAAO;CACrC,IAAI,wBAAQ,IAAI,aAAa,CAAC;CAM9B,MAAM,QAAQC,eAAAA,kBAAkB,IAAI;CACpC,MAAM,QAAQA,eAAAA,kBAAkB,IAAI;CAEpC,SAAS,eAAe,IAAY,IAAY,IAAY,IAAY,OAAqB;EAC3F,MAAM,SAAS,QAAQ,aAAa,KAAK,QAAQ,QAAQ,eAAe,IAAI,QAAQ;EACpF,MAAM,OAAOC,eAAAA,QAAQ,QAAQ,CAAC;EAC9B,KAAK,OAAO,YAAY;EACxB,KAAK,OAAO,YAAY;EACxB,KAAK,OAAO,YAAY;EACxB,KAAK,OAAO,YAAY;CAC1B;CAEA,SAAS,OAAO,QAAqB,OAAe,WAAyB;EAC3E,MAAM,EAAE,MAAM,WAAW;EACzB,IAAI,UAAU;EACd,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,GAAG;GAClC,IAAI,OAAO,KAAK,IAAI,KAAK,IAAI,EAAE;GAC/B,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;EACrC;EACA,IAAI,cAAc;EAClB,IAAI,YAAY;EAChB,IAAI,OAAO;CACb;;CAGA,SAAS,YAAY,OAAe,UAAkB,UAAkB,MAAoB;EAC1F,MAAM,EAAE,MAAM,MAAM,MAAM,SAAS,QAAQ;EAC3C,MAAM,OAAO,OAAO;EACpB,IAAI,MAAM,WAAW,MAAM,QAAQ,IAAI,aAAa,IAAI;EACxD,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,OAAO;GACnC,MAAM,IAAI,KAAK;GACf,MAAM,OAAO,KAAK,KAAK,OAAO,KAAK;EACrC;EAEA,IAAI,SAAS,GAAG;EAChB,MAAM,EAAE,UAAU,kBAAkB;EACpC,MAAM,UAAU,gBAAgB;EAEhC,MAAM,QAAQ,IAAI;EAClB,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO,WAAW,SAAS,QAAQ,CAAC;EAChE,MAAM,KAAK,KAAK,IAAI,OAAO,GAAG,KAAK,MAAM,WAAW,SAAS,QAAQ,CAAC;EACtE,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO,WAAW,SAAS,QAAQ,CAAC;EAChE,MAAM,KAAK,KAAK,IAAI,OAAO,GAAG,KAAK,MAAM,WAAW,SAAS,QAAQ,CAAC;EACtE,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK;GAC7B,MAAM,KAAK,IAAI,WAAW;GAC1B,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK;IAC7B,MAAM,KAAK,IAAI,WAAW;IAC1B,MAAM,IAAI,OAAO,MAAM,OAAO,KAAK,IAAI,EAAE,KAAK,KAAK,KAAK,MAAM,OAAO;GACvE;EACF;CACF;CAEA,OAAO;EACL,OAAO,MAAM;GACX,UAAUH,gBAAAA,mBAAmB,IAAI;GACjC,QAAQ,UAAU,OAAO;EAC3B;EACA,OAAO,OAAO;GACZ,MAAM,QAAQ,QAAQ,QAAQ,KAAK;GACnC,MAAM,EAAE,YAAY;GACpB,YAAY,OAAO,QAAQ,GAAG,QAAQ,GAAG,QAAQ,kBAAkB,QAAQ,SAAS;GAEpF,MAAM,SAAS,MAAM,SAAS;GAC9B,MAAM,EAAE,MAAM,SAAS,QAAQ;GAC/B,iBAAA,cAAc,OAAO,MAAM,MAAM,QAAQ,UAAU,QAAQ,WAAW,cAAc;GAEpF,IAAI,cAAc,QAAQ;GAC1B,OAAO,OAAO,QAAQ,WAAW,QAAQ,SAAS;GAClD,IAAI,QAAQ,aAAa,GAAG,OAAO,OAAO,QAAQ,gBAAgB,QAAQ,cAAc;EAC1F;CACF;AACF"}
@@ -0,0 +1,4 @@
1
+ import type { StyleRenderer } from '../../core/style.cjs';
2
+ import { type TopoOptions } from './options.cjs';
3
+ /** Marching squares on the CPU, stroked with the Canvas 2D API. */
4
+ export declare function createTopoCanvas2D(ctx: CanvasRenderingContext2D, initial: TopoOptions): StyleRenderer<TopoOptions>;
@@ -0,0 +1,4 @@
1
+ import type { StyleRenderer } from '../../core/style.js';
2
+ import { type TopoOptions } from './options.js';
3
+ /** Marching squares on the CPU, stroked with the Canvas 2D API. */
4
+ export declare function createTopoCanvas2D(ctx: CanvasRenderingContext2D, initial: TopoOptions): StyleRenderer<TopoOptions>;
@@ -0,0 +1,77 @@
1
+ import { createFloatBuffer, reserve } from "../../shared/buffer.js";
2
+ import { traceContours } from "./contours.js";
3
+ import { resolveTopoOptions } from "./options.js";
4
+ import { createTerrain } from "./terrain.js";
5
+ // src/styles/topo/canvas2d.ts
6
+ /** Marching squares on the CPU, stroked with the Canvas 2D API. */
7
+ function createTopoCanvas2D(ctx, initial) {
8
+ let options = resolveTopoOptions(initial);
9
+ const terrain = createTerrain(options);
10
+ let field = /* @__PURE__ */ new Float32Array(0);
11
+ const minor = createFloatBuffer(4096);
12
+ const major = createFloatBuffer(1024);
13
+ function collectSegment(x1, y1, x2, y2, level) {
14
+ const buffer = options.majorEvery > 0 && level % options.majorEvery === 0 ? major : minor;
15
+ const data = reserve(buffer, 4);
16
+ data[buffer.length++] = x1;
17
+ data[buffer.length++] = y1;
18
+ data[buffer.length++] = x2;
19
+ data[buffer.length++] = y2;
20
+ }
21
+ function stroke(buffer, color, lineWidth) {
22
+ const { data, length } = buffer;
23
+ ctx.beginPath();
24
+ for (let k = 0; k < length; k += 4) {
25
+ ctx.moveTo(data[k], data[k + 1]);
26
+ ctx.lineTo(data[k + 2], data[k + 3]);
27
+ }
28
+ ctx.strokeStyle = color;
29
+ ctx.lineWidth = lineWidth;
30
+ ctx.stroke();
31
+ }
32
+ /** Blend the keyframes and add the pointer bump into `field`. */
33
+ function sampleField(blend, pointerX, pointerY, bump) {
34
+ const { cols, rows, keyA, keyB } = terrain.field;
35
+ const size = cols * rows;
36
+ if (field.length !== size) field = new Float32Array(size);
37
+ for (let idx = 0; idx < size; idx++) {
38
+ const a = keyA[idx];
39
+ field[idx] = a + (keyB[idx] - a) * blend;
40
+ }
41
+ if (bump === 0) return;
42
+ const { cellSize, pointerRadius } = options;
43
+ const radius2 = pointerRadius * pointerRadius;
44
+ const reach = 3 * pointerRadius;
45
+ const i0 = Math.max(0, Math.floor((pointerX - reach) / cellSize));
46
+ const i1 = Math.min(cols - 1, Math.ceil((pointerX + reach) / cellSize));
47
+ const j0 = Math.max(0, Math.floor((pointerY - reach) / cellSize));
48
+ const j1 = Math.min(rows - 1, Math.ceil((pointerY + reach) / cellSize));
49
+ for (let j = j0; j <= j1; j++) {
50
+ const dy = j * cellSize - pointerY;
51
+ for (let i = i0; i <= i1; i++) {
52
+ const dx = i * cellSize - pointerX;
53
+ field[j * cols + i] += bump * Math.exp(-(dx * dx + dy * dy) / radius2);
54
+ }
55
+ }
56
+ }
57
+ return {
58
+ update(next) {
59
+ options = resolveTopoOptions(next);
60
+ terrain.configure(options);
61
+ },
62
+ render(frame) {
63
+ const blend = terrain.advance(frame);
64
+ const { pointer } = frame;
65
+ sampleField(blend, pointer.x, pointer.y, options.pointerStrength * pointer.influence);
66
+ minor.length = major.length = 0;
67
+ const { cols, rows } = terrain.field;
68
+ traceContours(field, cols, rows, options.cellSize, options.levelStep, collectSegment);
69
+ ctx.globalAlpha = options.lineOpacity;
70
+ stroke(minor, options.lineColor, options.lineWidth);
71
+ if (options.majorEvery > 0) stroke(major, options.majorLineColor, options.majorLineWidth);
72
+ }
73
+ };
74
+ }
75
+ export { createTopoCanvas2D };
76
+
77
+ //# sourceMappingURL=canvas2d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas2d.js","names":[],"sources":["../../../src/styles/topo/canvas2d.ts"],"sourcesContent":["import type { StyleRenderer } from '../../core/style';\nimport { createFloatBuffer, reserve, type FloatBuffer } from '../../shared/buffer';\nimport { traceContours } from './contours';\nimport { resolveTopoOptions, type TopoOptions } from './options';\nimport { createTerrain } from './terrain';\n\n/** Marching squares on the CPU, stroked with the Canvas 2D API. */\nexport function createTopoCanvas2D(ctx: CanvasRenderingContext2D, initial: TopoOptions): StyleRenderer<TopoOptions> {\n let options = resolveTopoOptions(initial);\n const terrain = createTerrain(options);\n let field = new Float32Array(0);\n\n // Segments are collected into reusable buffers and stroked from the context's\n // own path, so a frame allocates nothing. Path2D objects per frame pile up as\n // native garbage faster than the browser frees them.\n // Line segments as x1, y1, x2, y2.\n const minor = createFloatBuffer(4096);\n const major = createFloatBuffer(1024);\n\n function collectSegment(x1: number, y1: number, x2: number, y2: number, level: number): void {\n const buffer = options.majorEvery > 0 && level % options.majorEvery === 0 ? major : minor;\n const data = reserve(buffer, 4);\n data[buffer.length++] = x1;\n data[buffer.length++] = y1;\n data[buffer.length++] = x2;\n data[buffer.length++] = y2;\n }\n\n function stroke(buffer: FloatBuffer, color: string, lineWidth: number): void {\n const { data, length } = buffer;\n ctx.beginPath();\n for (let k = 0; k < length; k += 4) {\n ctx.moveTo(data[k], data[k + 1]);\n ctx.lineTo(data[k + 2], data[k + 3]);\n }\n ctx.strokeStyle = color;\n ctx.lineWidth = lineWidth;\n ctx.stroke();\n }\n\n /** Blend the keyframes and add the pointer bump into `field`. */\n function sampleField(blend: number, pointerX: number, pointerY: number, bump: number): void {\n const { cols, rows, keyA, keyB } = terrain.field;\n const size = cols * rows;\n if (field.length !== size) field = new Float32Array(size);\n for (let idx = 0; idx < size; idx++) {\n const a = keyA[idx];\n field[idx] = a + (keyB[idx] - a) * blend;\n }\n\n if (bump === 0) return;\n const { cellSize, pointerRadius } = options;\n const radius2 = pointerRadius * pointerRadius;\n // Beyond 3 radii the bump adds < 0.0001, so only touch the cells around the pointer.\n const reach = 3 * pointerRadius;\n const i0 = Math.max(0, Math.floor((pointerX - reach) / cellSize));\n const i1 = Math.min(cols - 1, Math.ceil((pointerX + reach) / cellSize));\n const j0 = Math.max(0, Math.floor((pointerY - reach) / cellSize));\n const j1 = Math.min(rows - 1, Math.ceil((pointerY + reach) / cellSize));\n for (let j = j0; j <= j1; j++) {\n const dy = j * cellSize - pointerY;\n for (let i = i0; i <= i1; i++) {\n const dx = i * cellSize - pointerX;\n field[j * cols + i] += bump * Math.exp(-(dx * dx + dy * dy) / radius2);\n }\n }\n }\n\n return {\n update(next) {\n options = resolveTopoOptions(next);\n terrain.configure(options);\n },\n render(frame) {\n const blend = terrain.advance(frame);\n const { pointer } = frame;\n sampleField(blend, pointer.x, pointer.y, options.pointerStrength * pointer.influence);\n\n minor.length = major.length = 0;\n const { cols, rows } = terrain.field;\n traceContours(field, cols, rows, options.cellSize, options.levelStep, collectSegment);\n\n ctx.globalAlpha = options.lineOpacity;\n stroke(minor, options.lineColor, options.lineWidth);\n if (options.majorEvery > 0) stroke(major, options.majorLineColor, options.majorLineWidth);\n },\n };\n}\n"],"mappings":";;;;;;AAOA,SAAgB,mBAAmB,KAA+B,SAAkD;CAClH,IAAI,UAAU,mBAAmB,OAAO;CACxC,MAAM,UAAU,cAAc,OAAO;CACrC,IAAI,wBAAQ,IAAI,aAAa,CAAC;CAM9B,MAAM,QAAQ,kBAAkB,IAAI;CACpC,MAAM,QAAQ,kBAAkB,IAAI;CAEpC,SAAS,eAAe,IAAY,IAAY,IAAY,IAAY,OAAqB;EAC3F,MAAM,SAAS,QAAQ,aAAa,KAAK,QAAQ,QAAQ,eAAe,IAAI,QAAQ;EACpF,MAAM,OAAO,QAAQ,QAAQ,CAAC;EAC9B,KAAK,OAAO,YAAY;EACxB,KAAK,OAAO,YAAY;EACxB,KAAK,OAAO,YAAY;EACxB,KAAK,OAAO,YAAY;CAC1B;CAEA,SAAS,OAAO,QAAqB,OAAe,WAAyB;EAC3E,MAAM,EAAE,MAAM,WAAW;EACzB,IAAI,UAAU;EACd,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK,GAAG;GAClC,IAAI,OAAO,KAAK,IAAI,KAAK,IAAI,EAAE;GAC/B,IAAI,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE;EACrC;EACA,IAAI,cAAc;EAClB,IAAI,YAAY;EAChB,IAAI,OAAO;CACb;;CAGA,SAAS,YAAY,OAAe,UAAkB,UAAkB,MAAoB;EAC1F,MAAM,EAAE,MAAM,MAAM,MAAM,SAAS,QAAQ;EAC3C,MAAM,OAAO,OAAO;EACpB,IAAI,MAAM,WAAW,MAAM,QAAQ,IAAI,aAAa,IAAI;EACxD,KAAK,IAAI,MAAM,GAAG,MAAM,MAAM,OAAO;GACnC,MAAM,IAAI,KAAK;GACf,MAAM,OAAO,KAAK,KAAK,OAAO,KAAK;EACrC;EAEA,IAAI,SAAS,GAAG;EAChB,MAAM,EAAE,UAAU,kBAAkB;EACpC,MAAM,UAAU,gBAAgB;EAEhC,MAAM,QAAQ,IAAI;EAClB,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO,WAAW,SAAS,QAAQ,CAAC;EAChE,MAAM,KAAK,KAAK,IAAI,OAAO,GAAG,KAAK,MAAM,WAAW,SAAS,QAAQ,CAAC;EACtE,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,OAAO,WAAW,SAAS,QAAQ,CAAC;EAChE,MAAM,KAAK,KAAK,IAAI,OAAO,GAAG,KAAK,MAAM,WAAW,SAAS,QAAQ,CAAC;EACtE,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK;GAC7B,MAAM,KAAK,IAAI,WAAW;GAC1B,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,KAAK;IAC7B,MAAM,KAAK,IAAI,WAAW;IAC1B,MAAM,IAAI,OAAO,MAAM,OAAO,KAAK,IAAI,EAAE,KAAK,KAAK,KAAK,MAAM,OAAO;GACvE;EACF;CACF;CAEA,OAAO;EACL,OAAO,MAAM;GACX,UAAU,mBAAmB,IAAI;GACjC,QAAQ,UAAU,OAAO;EAC3B;EACA,OAAO,OAAO;GACZ,MAAM,QAAQ,QAAQ,QAAQ,KAAK;GACnC,MAAM,EAAE,YAAY;GACpB,YAAY,OAAO,QAAQ,GAAG,QAAQ,GAAG,QAAQ,kBAAkB,QAAQ,SAAS;GAEpF,MAAM,SAAS,MAAM,SAAS;GAC9B,MAAM,EAAE,MAAM,SAAS,QAAQ;GAC/B,cAAc,OAAO,MAAM,MAAM,QAAQ,UAAU,QAAQ,WAAW,cAAc;GAEpF,IAAI,cAAc,QAAQ;GAC1B,OAAO,OAAO,QAAQ,WAAW,QAAQ,SAAS;GAClD,IAAI,QAAQ,aAAa,GAAG,OAAO,OAAO,QAAQ,gBAAgB,QAAQ,cAAc;EAC1F;CACF;AACF"}