@basementstudio/shader-lab 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.biome/plugins/README.md +21 -0
  2. package/.biome/plugins/no-anchor-element.grit +12 -0
  3. package/.biome/plugins/no-relative-parent-imports.grit +10 -0
  4. package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  5. package/.changeset/README.md +17 -0
  6. package/.changeset/config.json +11 -0
  7. package/.editorconfig +40 -0
  8. package/.env.example +81 -0
  9. package/.gitattributes +19 -0
  10. package/.github/workflows/canary.yml +80 -0
  11. package/.github/workflows/ci.yml +37 -0
  12. package/.github/workflows/release.yml +56 -0
  13. package/.tldrignore +84 -0
  14. package/.vscode/extensions.json +20 -0
  15. package/.vscode/settings.json +105 -0
  16. package/README.md +119 -0
  17. package/biome.json +249 -0
  18. package/bun.lock +1224 -0
  19. package/next.config.ts +131 -0
  20. package/package.json +73 -0
  21. package/packages/shader-lab-react/CHANGELOG.md +9 -0
  22. package/packages/shader-lab-react/README.md +119 -0
  23. package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
  24. package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
  25. package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
  26. package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
  27. package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
  28. package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
  29. package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
  30. package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
  31. package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
  32. package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
  33. package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
  34. package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
  35. package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
  36. package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
  37. package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
  38. package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
  39. package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
  40. package/packages/shader-lab-react/package.json +36 -0
  41. package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
  42. package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
  43. package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
  44. package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
  45. package/packages/shader-lab-react/src/easings.ts +4 -0
  46. package/packages/shader-lab-react/src/index.ts +35 -0
  47. package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
  48. package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
  49. package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
  50. package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
  51. package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
  52. package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
  53. package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
  54. package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
  55. package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
  56. package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
  57. package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
  58. package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
  59. package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
  60. package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
  61. package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
  62. package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
  63. package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
  64. package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
  65. package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
  66. package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
  67. package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
  68. package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
  69. package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
  70. package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
  71. package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
  72. package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
  73. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
  74. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
  75. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
  76. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
  77. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
  78. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
  79. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
  80. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
  81. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
  82. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
  83. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
  84. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
  85. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
  86. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
  87. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
  88. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
  89. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
  90. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
  91. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
  92. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
  93. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
  94. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
  95. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
  96. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
  97. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
  98. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
  99. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
  100. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
  101. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
  102. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
  103. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
  104. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
  105. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
  106. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
  107. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
  108. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
  109. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  110. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
  111. package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
  112. package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
  113. package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
  114. package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
  115. package/packages/shader-lab-react/src/timeline.ts +283 -0
  116. package/packages/shader-lab-react/src/types/editor.ts +5 -0
  117. package/packages/shader-lab-react/src/types.ts +141 -0
  118. package/packages/shader-lab-react/tsconfig.build.json +8 -0
  119. package/packages/shader-lab-react/tsconfig.json +21 -0
  120. package/postcss.config.mjs +5 -0
  121. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
  122. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
  123. package/public/assets/patterns/bars/1.svg +3 -0
  124. package/public/assets/patterns/bars/2.svg +3 -0
  125. package/public/assets/patterns/bars/3.svg +3 -0
  126. package/public/assets/patterns/bars/4.svg +3 -0
  127. package/public/assets/patterns/bars/5.svg +3 -0
  128. package/public/assets/patterns/bars/6.svg +3 -0
  129. package/public/assets/patterns/candles/1.svg +3 -0
  130. package/public/assets/patterns/candles/2.svg +3 -0
  131. package/public/assets/patterns/candles/3.svg +3 -0
  132. package/public/assets/patterns/candles/4.svg +3 -0
  133. package/public/assets/patterns/shapes/1.svg +3 -0
  134. package/public/assets/patterns/shapes/2.svg +3 -0
  135. package/public/assets/patterns/shapes/3.svg +3 -0
  136. package/public/assets/patterns/shapes/4.svg +4 -0
  137. package/public/assets/patterns/shapes/5.svg +3 -0
  138. package/public/assets/patterns/shapes/6.svg +4 -0
  139. package/public/fonts/geist/Geist-Mono.woff2 +0 -0
  140. package/public/textures/blue-noise.png +0 -0
  141. package/public/textures/crt-mask.png +0 -0
  142. package/src/app/design/page.tsx +398 -0
  143. package/src/app/favicon.ico +0 -0
  144. package/src/app/globals.css +280 -0
  145. package/src/app/layout.tsx +89 -0
  146. package/src/app/page.tsx +20 -0
  147. package/src/app/robots.ts +13 -0
  148. package/src/app/sitemap.ts +13 -0
  149. package/src/components/editor/editor-canvas-viewport.tsx +116 -0
  150. package/src/components/editor/editor-export-dialog.tsx +1177 -0
  151. package/src/components/editor/editor-timeline-overlay.tsx +983 -0
  152. package/src/components/editor/editor-topbar.tsx +287 -0
  153. package/src/components/editor/layer-sidebar.tsx +738 -0
  154. package/src/components/editor/properties-sidebar-content.tsx +574 -0
  155. package/src/components/editor/properties-sidebar-fields.tsx +389 -0
  156. package/src/components/editor/properties-sidebar-utils.ts +178 -0
  157. package/src/components/editor/properties-sidebar.tsx +421 -0
  158. package/src/components/ui/button/index.tsx +57 -0
  159. package/src/components/ui/color-picker/index.tsx +358 -0
  160. package/src/components/ui/glass-panel/index.tsx +45 -0
  161. package/src/components/ui/icon-button/index.tsx +46 -0
  162. package/src/components/ui/select/index.tsx +136 -0
  163. package/src/components/ui/slider/index.tsx +192 -0
  164. package/src/components/ui/toggle/index.tsx +34 -0
  165. package/src/components/ui/typography/index.tsx +61 -0
  166. package/src/components/ui/xy-pad/index.tsx +160 -0
  167. package/src/features/editor/components/editor-export-dialog.module.css +271 -0
  168. package/src/hooks/use-editor-renderer.ts +182 -0
  169. package/src/lib/app.ts +6 -0
  170. package/src/lib/cn.ts +7 -0
  171. package/src/lib/easings.ts +240 -0
  172. package/src/lib/editor/config/layer-registry.ts +2434 -0
  173. package/src/lib/editor/custom-shader/shared.ts +28 -0
  174. package/src/lib/editor/export.ts +420 -0
  175. package/src/lib/editor/history.ts +71 -0
  176. package/src/lib/editor/layers.ts +76 -0
  177. package/src/lib/editor/parameter-schema.ts +75 -0
  178. package/src/lib/editor/project-file.ts +145 -0
  179. package/src/lib/editor/shader-export-snippet.ts +37 -0
  180. package/src/lib/editor/shader-export.ts +315 -0
  181. package/src/lib/editor/timeline/evaluate.ts +252 -0
  182. package/src/lib/editor/view-transform.ts +58 -0
  183. package/src/lib/fonts.ts +28 -0
  184. package/src/renderer/ascii-atlas.ts +83 -0
  185. package/src/renderer/ascii-pass.ts +416 -0
  186. package/src/renderer/blend-modes.ts +229 -0
  187. package/src/renderer/contracts.ts +161 -0
  188. package/src/renderer/create-webgpu-renderer.ts +48 -0
  189. package/src/renderer/crt-pass.ts +1040 -0
  190. package/src/renderer/custom-shader-pass.ts +117 -0
  191. package/src/renderer/custom-shader-runtime.ts +309 -0
  192. package/src/renderer/dither-textures.ts +99 -0
  193. package/src/renderer/dithering-pass.ts +322 -0
  194. package/src/renderer/gradient-pass.ts +520 -0
  195. package/src/renderer/halftone-pass.ts +932 -0
  196. package/src/renderer/ink-pass.ts +683 -0
  197. package/src/renderer/live-pass.ts +194 -0
  198. package/src/renderer/media-pass.ts +187 -0
  199. package/src/renderer/media-texture.ts +66 -0
  200. package/src/renderer/particle-grid-pass.ts +389 -0
  201. package/src/renderer/pass-node-factory.ts +33 -0
  202. package/src/renderer/pass-node.ts +209 -0
  203. package/src/renderer/pattern-atlas.ts +97 -0
  204. package/src/renderer/pattern-pass.ts +552 -0
  205. package/src/renderer/pipeline-manager.ts +343 -0
  206. package/src/renderer/pixel-sorting-pass.ts +277 -0
  207. package/src/renderer/project-clock.ts +57 -0
  208. package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
  209. package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
  210. package/src/renderer/shaders/tsl/noise/common.ts +30 -0
  211. package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
  212. package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
  213. package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
  214. package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
  215. package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
  216. package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
  217. package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
  218. package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
  219. package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
  220. package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
  221. package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
  222. package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
  223. package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
  224. package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
  225. package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
  226. package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
  227. package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
  228. package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
  229. package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
  230. package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
  231. package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
  232. package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
  233. package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
  234. package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
  235. package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
  236. package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
  237. package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
  238. package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
  239. package/src/renderer/shaders/tsl/utils/index.ts +47 -0
  240. package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
  241. package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
  242. package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
  243. package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
  244. package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
  245. package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
  246. package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  247. package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
  248. package/src/renderer/text-pass.ts +176 -0
  249. package/src/store/asset-store.ts +193 -0
  250. package/src/store/editor-store.ts +223 -0
  251. package/src/store/history-store.ts +172 -0
  252. package/src/store/index.ts +31 -0
  253. package/src/store/layer-store.ts +675 -0
  254. package/src/store/timeline-store.ts +572 -0
  255. package/src/types/assets.d.ts +6 -0
  256. package/src/types/css.d.ts +21 -0
  257. package/src/types/editor.ts +357 -0
  258. package/src/types/react.d.ts +15 -0
  259. package/src/types/three-tsl.d.ts +146 -0
  260. package/src/types/three-webgpu.d.ts +51 -0
  261. package/tsconfig.json +49 -0
@@ -0,0 +1,51 @@
1
+ declare module "three/webgpu" {
2
+ export * from "three"
3
+
4
+ import type {
5
+ Camera,
6
+ ColorRepresentation,
7
+ Material,
8
+ Scene,
9
+ TypedArray,
10
+ WebGLRendererParameters,
11
+ WebGLRenderTarget,
12
+ } from "three"
13
+ import type { TSLNode } from "three/tsl"
14
+
15
+ export class MeshBasicNodeMaterial extends Material {
16
+ colorNode: TSLNode | null
17
+ opacityNode: TSLNode | null
18
+ positionNode: TSLNode | null
19
+ }
20
+
21
+ export class PointsNodeMaterial extends Material {
22
+ colorNode: TSLNode | null
23
+ opacityNode: TSLNode | null
24
+ positionNode: TSLNode | null
25
+ sizeNode: TSLNode | null
26
+ alphaTest: number
27
+ transparent: boolean
28
+ depthWrite: boolean
29
+ sizeAttenuation: boolean
30
+ }
31
+
32
+ export class WebGPURenderer {
33
+ constructor(options?: WebGLRendererParameters & { canvas?: HTMLCanvasElement })
34
+
35
+ dispose(): void
36
+ init(): Promise<void>
37
+ readRenderTargetPixelsAsync(
38
+ target: WebGLRenderTarget,
39
+ x: number,
40
+ y: number,
41
+ width: number,
42
+ height: number,
43
+ ): Promise<TypedArray>
44
+ render(scene: Scene, camera: Camera): void
45
+ setAnimationLoop(callback: ((time: number) => void) | null): void
46
+ setClearColor(color: ColorRepresentation, alpha?: number): void
47
+ setPixelRatio(pixelRatio: number): void
48
+ setRenderTarget(target: WebGLRenderTarget | null): void
49
+ setSize(width: number, height: number, updateStyle?: boolean): void
50
+ }
51
+ }
@@ -0,0 +1,4 @@
1
+ export const easings = {
2
+ easeInOutCubic: (x: number): number =>
3
+ x < 0.5 ? 4 * x * x * x : 1 - ((-2 * x + 2) ** 3) / 2,
4
+ }
@@ -0,0 +1,35 @@
1
+ export {
2
+ ShaderLabComposition,
3
+ type ShaderLabCompositionProps,
4
+ } from "./shader-lab-composition"
5
+ export {
6
+ advanceRuntimeClock,
7
+ createRuntimeClock,
8
+ type ShaderLabRuntimeClock,
9
+ } from "./runtime-clock"
10
+ export { buildRuntimeFrame, type ShaderLabRuntimeFrame } from "./runtime-frame"
11
+ export {
12
+ evaluateTimelineForLayers,
13
+ resolveEvaluatedLayers,
14
+ type EvaluatedLayerState,
15
+ } from "./timeline"
16
+ export type {
17
+ ShaderLabBlendMode,
18
+ ShaderLabAnimatedPropertyBinding,
19
+ ShaderLabAssetSource,
20
+ ShaderLabCompositeMode,
21
+ ShaderLabConfig,
22
+ ShaderLabEffectLayerType,
23
+ ShaderLabInlineSketchSource,
24
+ ShaderLabLayerKind,
25
+ ShaderLabLayerConfig,
26
+ ShaderLabLayerType,
27
+ ShaderLabModuleSketchSource,
28
+ ShaderLabParameterValue,
29
+ ShaderLabSketchSource,
30
+ ShaderLabSourceLayerType,
31
+ ShaderLabTimelineConfig,
32
+ ShaderLabTimelineInterpolation,
33
+ ShaderLabTimelineKeyframe,
34
+ ShaderLabTimelineTrack,
35
+ } from "./types"
@@ -0,0 +1,2 @@
1
+ export const CUSTOM_SHADER_ENTRY_EXPORT = "sketch"
2
+
@@ -0,0 +1,83 @@
1
+ import * as THREE from "three/webgpu"
2
+
3
+ export const ASCII_CHARSETS: Record<string, string> = {
4
+ binary: "01",
5
+ blocks: " ░▒▓█",
6
+ dense: " .',:;!|({#@",
7
+ hatching: " ╱╲╳░▒",
8
+ light: " .:-=+*#%@",
9
+ }
10
+
11
+ export type AsciiFontWeight = "thin" | "regular" | "bold"
12
+ export const DEFAULT_ASCII_CHARS = " .:-=+*#%@"
13
+
14
+ function normalizeChars(chars: string): string {
15
+ return chars.length > 0 ? chars : " "
16
+ }
17
+
18
+ export function buildAsciiAtlas(
19
+ chars: string,
20
+ fontWeight: AsciiFontWeight = "regular",
21
+ cellPx = 16,
22
+ ): THREE.CanvasTexture {
23
+ const normalizedChars = normalizeChars(chars)
24
+ const cellSize = Math.max(4, Math.round(cellPx))
25
+ const fontSize = Math.max(4, Math.floor(cellSize * 0.9))
26
+ const canvas = document.createElement("canvas")
27
+ canvas.width = normalizedChars.length * cellSize
28
+ canvas.height = cellSize
29
+
30
+ const context = canvas.getContext("2d")
31
+
32
+ if (!context) {
33
+ throw new Error("Unable to create 2D context for ASCII atlas")
34
+ }
35
+
36
+ context.imageSmoothingEnabled = false
37
+ context.fillStyle = "#000"
38
+ context.fillRect(0, 0, canvas.width, canvas.height)
39
+
40
+ const weightMap: Record<AsciiFontWeight, string> = {
41
+ bold: "700",
42
+ regular: "400",
43
+ thin: "100",
44
+ }
45
+
46
+ context.fillStyle = "#fff"
47
+ context.font = `${weightMap[fontWeight]} ${fontSize}px "Geist Mono", monospace`
48
+ context.textAlign = "center"
49
+ context.textBaseline = "alphabetic"
50
+
51
+ for (const [index, char] of [...normalizedChars].entries()) {
52
+ const metrics = context.measureText(char)
53
+ const x = Math.round((index + 0.5) * cellSize)
54
+ const y = Math.round(
55
+ (cellSize + metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) * 0.5,
56
+ )
57
+ context.fillText(char, x, y)
58
+ }
59
+
60
+ const image = context.getImageData(0, 0, canvas.width, canvas.height)
61
+ const { data } = image
62
+
63
+ for (let index = 0; index < data.length; index += 4) {
64
+ const mask = (data[index] ?? 0) > 96 ? 255 : 0
65
+ data[index] = mask
66
+ data[index + 1] = mask
67
+ data[index + 2] = mask
68
+ data[index + 3] = 255
69
+ }
70
+
71
+ context.putImageData(image, 0, 0)
72
+
73
+ const texture = new THREE.CanvasTexture(canvas)
74
+ texture.flipY = false
75
+ texture.generateMipmaps = false
76
+ texture.magFilter = THREE.NearestFilter
77
+ texture.minFilter = THREE.NearestFilter
78
+ texture.wrapS = THREE.ClampToEdgeWrapping
79
+ texture.wrapT = THREE.ClampToEdgeWrapping
80
+ texture.needsUpdate = true
81
+
82
+ return texture
83
+ }
@@ -0,0 +1,416 @@
1
+ import * as THREE from "three/webgpu"
2
+ import { bloom } from "three/examples/jsm/tsl/display/BloomNode.js"
3
+ import {
4
+ clamp,
5
+ float,
6
+ floor,
7
+ mix,
8
+ mod,
9
+ select,
10
+ texture as tslTexture,
11
+ type TSLNode,
12
+ uniform,
13
+ uv,
14
+ vec2,
15
+ vec3,
16
+ vec4,
17
+ } from "three/tsl"
18
+ import {
19
+ ASCII_CHARSETS,
20
+ buildAsciiAtlas,
21
+ type AsciiFontWeight,
22
+ DEFAULT_ASCII_CHARS,
23
+ } from "./ascii-atlas"
24
+ import { PassNode } from "./pass-node"
25
+ import type { LayerParameterValues } from "../types/editor"
26
+
27
+ type Node = TSLNode
28
+ type AsciiColorMode = "green-terminal" | "monochrome" | "source"
29
+ type AsciiCharset = keyof typeof ASCII_CHARSETS | "custom"
30
+
31
+ function clamp01(value: number): number {
32
+ return Math.max(0, Math.min(1, value))
33
+ }
34
+
35
+ function parseCssColorRgb(value: string): [number, number, number] {
36
+ const rgba = value.match(
37
+ /rgba?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+)?\s*\)/i,
38
+ )
39
+
40
+ if (rgba) {
41
+ return [
42
+ clamp01(Number.parseFloat(rgba[1] ?? "0") / 255),
43
+ clamp01(Number.parseFloat(rgba[2] ?? "0") / 255),
44
+ clamp01(Number.parseFloat(rgba[3] ?? "0") / 255),
45
+ ]
46
+ }
47
+
48
+ const hex = value.trim().replace("#", "")
49
+
50
+ if (hex.length === 6) {
51
+ return [
52
+ Number.parseInt(hex.slice(0, 2), 16) / 255,
53
+ Number.parseInt(hex.slice(2, 4), 16) / 255,
54
+ Number.parseInt(hex.slice(4, 6), 16) / 255,
55
+ ]
56
+ }
57
+
58
+ if (hex.length === 3) {
59
+ return [
60
+ Number.parseInt(`${hex[0] ?? "0"}${hex[0] ?? "0"}`, 16) / 255,
61
+ Number.parseInt(`${hex[1] ?? "0"}${hex[1] ?? "0"}`, 16) / 255,
62
+ Number.parseInt(`${hex[2] ?? "0"}${hex[2] ?? "0"}`, 16) / 255,
63
+ ]
64
+ }
65
+
66
+ return [1, 1, 1]
67
+ }
68
+
69
+ export class AsciiPass extends PassNode {
70
+ private atlasTexture: THREE.CanvasTexture | null = null
71
+ private atlasTextureNodes: Node[] = []
72
+ private bloomEnabled = false
73
+ private bloomNode: ReturnType<typeof bloom> | null = null
74
+ private readonly bloomIntensityUniform: Node
75
+ private readonly bloomRadiusUniform: Node
76
+ private readonly bloomSoftnessUniform: Node
77
+ private readonly bloomThresholdUniform: Node
78
+ private readonly cellSizeUniform: Node
79
+ private readonly bgOpacityUniform: Node
80
+ private readonly colorModeUniform: Node
81
+ private readonly invertUniform: Node
82
+ private readonly monoBlueUniform: Node
83
+ private readonly monoGreenUniform: Node
84
+ private readonly logicalHeightUniform: Node
85
+ private readonly logicalWidthUniform: Node
86
+ private readonly monoRedUniform: Node
87
+ private readonly numCharsUniform: Node
88
+ private readonly placeholder: THREE.Texture
89
+ private sourceTextureNodes: Node[] = []
90
+
91
+ private currentCellSize = 12
92
+ private currentCharset: AsciiCharset = "light"
93
+ private currentCustomChars = DEFAULT_ASCII_CHARS
94
+ private currentFontWeight: AsciiFontWeight = "regular"
95
+
96
+ constructor(layerId: string) {
97
+ super(layerId)
98
+ this.placeholder = new THREE.Texture()
99
+ this.bloomIntensityUniform = uniform(1.25)
100
+ this.bloomRadiusUniform = uniform(6)
101
+ this.bloomSoftnessUniform = uniform(0.35)
102
+ this.bloomThresholdUniform = uniform(0.6)
103
+ this.cellSizeUniform = uniform(12)
104
+ this.logicalWidthUniform = uniform(1)
105
+ this.logicalHeightUniform = uniform(1)
106
+ this.numCharsUniform = uniform(DEFAULT_ASCII_CHARS.length)
107
+ this.colorModeUniform = uniform(1)
108
+ this.monoRedUniform = uniform(0.96)
109
+ this.monoGreenUniform = uniform(0.96)
110
+ this.monoBlueUniform = uniform(0.94)
111
+ this.bgOpacityUniform = uniform(0)
112
+ this.invertUniform = uniform(0)
113
+ this.atlasTexture = buildAsciiAtlas(DEFAULT_ASCII_CHARS, "regular", this.currentCellSize)
114
+ this.rebuildEffectNode()
115
+ }
116
+
117
+ override render(
118
+ renderer: THREE.WebGPURenderer,
119
+ inputTexture: THREE.Texture,
120
+ outputTarget: THREE.WebGLRenderTarget,
121
+ time: number,
122
+ delta: number,
123
+ ): void {
124
+ for (const sourceTextureNode of this.sourceTextureNodes) {
125
+ sourceTextureNode.value = inputTexture
126
+ }
127
+
128
+ if (this.atlasTexture) {
129
+ for (const atlasTextureNode of this.atlasTextureNodes) {
130
+ atlasTextureNode.value = this.atlasTexture
131
+ }
132
+ }
133
+
134
+ super.render(renderer, inputTexture, outputTarget, time, delta)
135
+ }
136
+
137
+ override updateParams(params: LayerParameterValues): void {
138
+ const nextCellSize =
139
+ typeof params.cellSize === "number" ? Math.max(4, Math.round(params.cellSize)) : 12
140
+ const nextCharset = this.resolveCharset(params.charset)
141
+ const nextCustomChars =
142
+ typeof params.customChars === "string" ? params.customChars : DEFAULT_ASCII_CHARS
143
+ const nextFontWeight = this.resolveFontWeight(params.fontWeight)
144
+ const nextColorMode = this.resolveColorMode(params.colorMode)
145
+ const nextBgOpacity =
146
+ typeof params.bgOpacity === "number" ? clamp01(params.bgOpacity) : 0
147
+ const nextBloomEnabled = params.bloomEnabled === true
148
+ const nextBloomIntensity =
149
+ typeof params.bloomIntensity === "number" ? Math.max(0, params.bloomIntensity) : 1.25
150
+ const nextBloomThreshold =
151
+ typeof params.bloomThreshold === "number" ? clamp01(params.bloomThreshold) : 0.6
152
+ const nextBloomRadius =
153
+ typeof params.bloomRadius === "number" ? Math.max(0, params.bloomRadius) : 6
154
+ const nextBloomSoftness =
155
+ typeof params.bloomSoftness === "number" ? clamp01(params.bloomSoftness) : 0.35
156
+ const [red, green, blue] = parseCssColorRgb(
157
+ typeof params.monoColor === "string" ? params.monoColor : "#f5f5f0",
158
+ )
159
+
160
+ this.cellSizeUniform.value = nextCellSize
161
+ this.bgOpacityUniform.value = nextBgOpacity
162
+ this.bloomIntensityUniform.value = nextBloomIntensity
163
+ this.bloomRadiusUniform.value = nextBloomRadius
164
+ this.bloomSoftnessUniform.value = nextBloomSoftness
165
+ this.bloomThresholdUniform.value = nextBloomThreshold
166
+ this.invertUniform.value = params.invert === true ? 1 : 0
167
+ this.monoRedUniform.value = red
168
+ this.monoGreenUniform.value = green
169
+ this.monoBlueUniform.value = blue
170
+
171
+ this.colorModeUniform.value = this.getColorModeValue(nextColorMode)
172
+
173
+ const needsAtlasRebuild =
174
+ nextCellSize !== this.currentCellSize ||
175
+ nextCharset !== this.currentCharset ||
176
+ nextFontWeight !== this.currentFontWeight ||
177
+ (nextCharset === "custom" && nextCustomChars !== this.currentCustomChars)
178
+
179
+ this.currentCellSize = nextCellSize
180
+ this.currentCharset = nextCharset
181
+ this.currentCustomChars = nextCustomChars
182
+ this.currentFontWeight = nextFontWeight
183
+
184
+ if (nextBloomEnabled !== this.bloomEnabled) {
185
+ this.bloomEnabled = nextBloomEnabled
186
+ this.rebuildEffectNode()
187
+ return
188
+ }
189
+
190
+ if (this.bloomNode) {
191
+ this.bloomNode.strength.value = nextBloomIntensity
192
+ this.bloomNode.radius.value = this.normalizeBloomRadius(nextBloomRadius)
193
+ this.bloomNode.threshold.value = nextBloomThreshold
194
+ this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(nextBloomSoftness)
195
+ }
196
+
197
+ if (needsAtlasRebuild) {
198
+ this.rebuildAtlas()
199
+ }
200
+ }
201
+
202
+ override dispose(): void {
203
+ this.disposeBloomNode()
204
+ this.placeholder.dispose()
205
+ this.atlasTexture?.dispose()
206
+ super.dispose()
207
+ }
208
+
209
+ override updateLogicalSize(width: number, height: number): void {
210
+ this.logicalWidthUniform.value = Math.max(1, width)
211
+ this.logicalHeightUniform.value = Math.max(1, height)
212
+ }
213
+
214
+ protected override buildEffectNode(): Node {
215
+ if (!(this.cellSizeUniform && this.numCharsUniform && this.placeholder)) {
216
+ return this.inputNode
217
+ }
218
+
219
+ this.disposeBloomNode()
220
+ this.bloomNode = null
221
+ this.sourceTextureNodes = []
222
+ this.atlasTextureNodes = []
223
+
224
+ const renderTargetUv = vec2(uv().x, float(1).sub(uv().y))
225
+ const logicalScreenSize = vec2(this.logicalWidthUniform, this.logicalHeightUniform)
226
+ const normalizedCellSize = vec2(this.cellSizeUniform, this.cellSizeUniform).div(
227
+ logicalScreenSize,
228
+ )
229
+ const sampleAscii = (sampleUv: Node) => {
230
+ const safeUv = clamp(sampleUv, vec2(float(0), float(0)), vec2(float(1), float(1)))
231
+ const screenPixel = floor(safeUv.mul(logicalScreenSize))
232
+ const cellCenterUv = floor(safeUv.div(normalizedCellSize))
233
+ .add(vec2(0.5, 0.5))
234
+ .mul(normalizedCellSize)
235
+ const localCellPixel = vec2(
236
+ mod(screenPixel.x, this.cellSizeUniform),
237
+ mod(screenPixel.y, this.cellSizeUniform),
238
+ )
239
+ const sampledColor = this.trackSourceTextureNode(cellCenterUv)
240
+ const luma = float(sampledColor.r)
241
+ .mul(float(0.2126))
242
+ .add(float(sampledColor.g).mul(float(0.7152)))
243
+ .add(float(sampledColor.b).mul(float(0.0722)))
244
+ const adjustedLuma = select(
245
+ this.invertUniform.greaterThan(float(0.5)),
246
+ float(1).sub(luma),
247
+ luma,
248
+ )
249
+ const charIndex = floor(
250
+ clamp(
251
+ adjustedLuma.mul(this.numCharsUniform.sub(float(1))),
252
+ float(0),
253
+ this.numCharsUniform.sub(float(1)),
254
+ ),
255
+ )
256
+ const atlasUv = vec2(
257
+ charIndex
258
+ .mul(this.cellSizeUniform)
259
+ .add(localCellPixel.x)
260
+ .add(float(0.5))
261
+ .div(this.numCharsUniform.mul(this.cellSizeUniform)),
262
+ localCellPixel.y.add(float(0.5)).div(this.cellSizeUniform),
263
+ )
264
+ const characterMask = float(this.trackAtlasTextureNode(atlasUv).r)
265
+ const sourceColor = vec3(
266
+ float(sampledColor.r),
267
+ float(sampledColor.g),
268
+ float(sampledColor.b),
269
+ )
270
+ const monoTint = vec3(
271
+ this.monoRedUniform,
272
+ this.monoGreenUniform,
273
+ this.monoBlueUniform,
274
+ )
275
+ const monochromeColor = monoTint.mul(adjustedLuma)
276
+ const greenTerminalColor = vec3(float(0), adjustedLuma, float(0))
277
+ const glyphColor = select(
278
+ this.colorModeUniform.lessThan(float(0.5)),
279
+ sourceColor,
280
+ select(
281
+ this.colorModeUniform.lessThan(float(1.5)),
282
+ monochromeColor,
283
+ greenTerminalColor,
284
+ ),
285
+ )
286
+ const sourceBackground = sourceColor.mul(this.bgOpacityUniform)
287
+ const backgroundColor = select(
288
+ this.colorModeUniform.lessThan(float(0.5)),
289
+ sourceBackground,
290
+ vec3(float(0), float(0), float(0)),
291
+ )
292
+
293
+ return {
294
+ baseColor: mix(backgroundColor, glyphColor, characterMask),
295
+ emissiveColor: glyphColor.mul(characterMask),
296
+ }
297
+ }
298
+
299
+ const baseSample = sampleAscii(renderTargetUv)
300
+
301
+ if (!this.bloomEnabled) {
302
+ return vec4(baseSample.baseColor, float(1))
303
+ }
304
+
305
+ const bloomInput = vec4(baseSample.emissiveColor, float(1))
306
+ this.bloomNode = bloom(
307
+ bloomInput,
308
+ this.bloomIntensityUniform.value as number,
309
+ this.normalizeBloomRadius(this.bloomRadiusUniform.value as number),
310
+ this.bloomThresholdUniform.value as number,
311
+ )
312
+ this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(
313
+ this.bloomSoftnessUniform.value as number,
314
+ )
315
+
316
+ return vec4(
317
+ clamp(
318
+ baseSample.baseColor.add(this.getBloomTextureNode().rgb),
319
+ vec3(float(0), float(0), float(0)),
320
+ vec3(float(1), float(1), float(1)),
321
+ ),
322
+ float(1),
323
+ )
324
+ }
325
+
326
+ private getActiveChars(): string {
327
+ return this.currentCharset === "custom"
328
+ ? this.currentCustomChars || " "
329
+ : ASCII_CHARSETS[this.currentCharset] ?? DEFAULT_ASCII_CHARS
330
+ }
331
+
332
+ private getColorModeValue(colorMode: AsciiColorMode): number {
333
+ switch (colorMode) {
334
+ case "source":
335
+ return 0
336
+ case "green-terminal":
337
+ return 2
338
+ default:
339
+ return 1
340
+ }
341
+ }
342
+
343
+ private rebuildAtlas(): void {
344
+ const chars = this.getActiveChars()
345
+ this.atlasTexture?.dispose()
346
+ this.atlasTexture = buildAsciiAtlas(chars, this.currentFontWeight, this.currentCellSize)
347
+ this.numCharsUniform.value = chars.length
348
+ this.rebuildEffectNode()
349
+ }
350
+
351
+ private resolveCharset(value: unknown): AsciiCharset {
352
+ return value === "binary" ||
353
+ value === "blocks" ||
354
+ value === "custom" ||
355
+ value === "dense" ||
356
+ value === "hatching" ||
357
+ value === "light"
358
+ ? value
359
+ : "light"
360
+ }
361
+
362
+ private resolveColorMode(value: unknown): AsciiColorMode {
363
+ return value === "green-terminal" || value === "source" ? value : "monochrome"
364
+ }
365
+
366
+ private resolveFontWeight(value: unknown): AsciiFontWeight {
367
+ return value === "bold" || value === "thin" ? value : "regular"
368
+ }
369
+
370
+ private normalizeBloomRadius(value: number): number {
371
+ return clamp01(value / 24)
372
+ }
373
+
374
+ private normalizeBloomSoftness(value: number): number {
375
+ return Math.max(0.001, value * 0.25)
376
+ }
377
+
378
+ private disposeBloomNode(): void {
379
+ ;(this.bloomNode as { dispose?: () => void } | null)?.dispose?.()
380
+ }
381
+
382
+ private getBloomTextureNode(): Node {
383
+ const bloomNode = this.bloomNode as
384
+ | ({
385
+ getTexture?: () => Node
386
+ getTextureNode?: () => Node
387
+ } & object)
388
+ | null
389
+
390
+ if (!bloomNode) {
391
+ throw new Error("Bloom node is not initialized")
392
+ }
393
+
394
+ if ("getTextureNode" in bloomNode && typeof bloomNode.getTextureNode === "function") {
395
+ return bloomNode.getTextureNode()
396
+ }
397
+
398
+ if ("getTexture" in bloomNode && typeof bloomNode.getTexture === "function") {
399
+ return bloomNode.getTexture()
400
+ }
401
+
402
+ throw new Error("Bloom node does not expose a texture getter")
403
+ }
404
+
405
+ private trackAtlasTextureNode(uvNode: Node): Node {
406
+ const atlasTextureNode = tslTexture(this.atlasTexture ?? new THREE.Texture(), uvNode)
407
+ this.atlasTextureNodes.push(atlasTextureNode)
408
+ return atlasTextureNode
409
+ }
410
+
411
+ private trackSourceTextureNode(uvNode: Node): Node {
412
+ const sourceTextureNode = tslTexture(this.placeholder, uvNode)
413
+ this.sourceTextureNodes.push(sourceTextureNode)
414
+ return sourceTextureNode
415
+ }
416
+ }
@@ -0,0 +1,3 @@
1
+ export function resolvePackageAssetUrl(relativePath: string): string {
2
+ return new URL(`../../../assets/${relativePath}`, import.meta.url).toString()
3
+ }