@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.
- package/.biome/plugins/README.md +21 -0
- package/.biome/plugins/no-anchor-element.grit +12 -0
- package/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/.changeset/README.md +17 -0
- package/.changeset/config.json +11 -0
- package/.editorconfig +40 -0
- package/.env.example +81 -0
- package/.gitattributes +19 -0
- package/.github/workflows/canary.yml +80 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/release.yml +56 -0
- package/.tldrignore +84 -0
- package/.vscode/extensions.json +20 -0
- package/.vscode/settings.json +105 -0
- package/README.md +119 -0
- package/biome.json +249 -0
- package/bun.lock +1224 -0
- package/next.config.ts +131 -0
- package/package.json +73 -0
- package/packages/shader-lab-react/CHANGELOG.md +9 -0
- package/packages/shader-lab-react/README.md +119 -0
- package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
- package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
- package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
- package/packages/shader-lab-react/package.json +36 -0
- package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
- package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
- package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
- package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
- package/packages/shader-lab-react/src/easings.ts +4 -0
- package/packages/shader-lab-react/src/index.ts +35 -0
- package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
- package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
- package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
- package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
- package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
- package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
- package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
- package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
- package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
- package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
- package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
- package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
- package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
- package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
- package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
- package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
- package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
- package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
- package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
- package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
- package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
- package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
- package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
- package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
- package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
- package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
- package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
- package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
- package/packages/shader-lab-react/src/timeline.ts +283 -0
- package/packages/shader-lab-react/src/types/editor.ts +5 -0
- package/packages/shader-lab-react/src/types.ts +141 -0
- package/packages/shader-lab-react/tsconfig.build.json +8 -0
- package/packages/shader-lab-react/tsconfig.json +21 -0
- package/postcss.config.mjs +5 -0
- package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
- package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
- package/public/assets/patterns/bars/1.svg +3 -0
- package/public/assets/patterns/bars/2.svg +3 -0
- package/public/assets/patterns/bars/3.svg +3 -0
- package/public/assets/patterns/bars/4.svg +3 -0
- package/public/assets/patterns/bars/5.svg +3 -0
- package/public/assets/patterns/bars/6.svg +3 -0
- package/public/assets/patterns/candles/1.svg +3 -0
- package/public/assets/patterns/candles/2.svg +3 -0
- package/public/assets/patterns/candles/3.svg +3 -0
- package/public/assets/patterns/candles/4.svg +3 -0
- package/public/assets/patterns/shapes/1.svg +3 -0
- package/public/assets/patterns/shapes/2.svg +3 -0
- package/public/assets/patterns/shapes/3.svg +3 -0
- package/public/assets/patterns/shapes/4.svg +4 -0
- package/public/assets/patterns/shapes/5.svg +3 -0
- package/public/assets/patterns/shapes/6.svg +4 -0
- package/public/fonts/geist/Geist-Mono.woff2 +0 -0
- package/public/textures/blue-noise.png +0 -0
- package/public/textures/crt-mask.png +0 -0
- package/src/app/design/page.tsx +398 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +280 -0
- package/src/app/layout.tsx +89 -0
- package/src/app/page.tsx +20 -0
- package/src/app/robots.ts +13 -0
- package/src/app/sitemap.ts +13 -0
- package/src/components/editor/editor-canvas-viewport.tsx +116 -0
- package/src/components/editor/editor-export-dialog.tsx +1177 -0
- package/src/components/editor/editor-timeline-overlay.tsx +983 -0
- package/src/components/editor/editor-topbar.tsx +287 -0
- package/src/components/editor/layer-sidebar.tsx +738 -0
- package/src/components/editor/properties-sidebar-content.tsx +574 -0
- package/src/components/editor/properties-sidebar-fields.tsx +389 -0
- package/src/components/editor/properties-sidebar-utils.ts +178 -0
- package/src/components/editor/properties-sidebar.tsx +421 -0
- package/src/components/ui/button/index.tsx +57 -0
- package/src/components/ui/color-picker/index.tsx +358 -0
- package/src/components/ui/glass-panel/index.tsx +45 -0
- package/src/components/ui/icon-button/index.tsx +46 -0
- package/src/components/ui/select/index.tsx +136 -0
- package/src/components/ui/slider/index.tsx +192 -0
- package/src/components/ui/toggle/index.tsx +34 -0
- package/src/components/ui/typography/index.tsx +61 -0
- package/src/components/ui/xy-pad/index.tsx +160 -0
- package/src/features/editor/components/editor-export-dialog.module.css +271 -0
- package/src/hooks/use-editor-renderer.ts +182 -0
- package/src/lib/app.ts +6 -0
- package/src/lib/cn.ts +7 -0
- package/src/lib/easings.ts +240 -0
- package/src/lib/editor/config/layer-registry.ts +2434 -0
- package/src/lib/editor/custom-shader/shared.ts +28 -0
- package/src/lib/editor/export.ts +420 -0
- package/src/lib/editor/history.ts +71 -0
- package/src/lib/editor/layers.ts +76 -0
- package/src/lib/editor/parameter-schema.ts +75 -0
- package/src/lib/editor/project-file.ts +145 -0
- package/src/lib/editor/shader-export-snippet.ts +37 -0
- package/src/lib/editor/shader-export.ts +315 -0
- package/src/lib/editor/timeline/evaluate.ts +252 -0
- package/src/lib/editor/view-transform.ts +58 -0
- package/src/lib/fonts.ts +28 -0
- package/src/renderer/ascii-atlas.ts +83 -0
- package/src/renderer/ascii-pass.ts +416 -0
- package/src/renderer/blend-modes.ts +229 -0
- package/src/renderer/contracts.ts +161 -0
- package/src/renderer/create-webgpu-renderer.ts +48 -0
- package/src/renderer/crt-pass.ts +1040 -0
- package/src/renderer/custom-shader-pass.ts +117 -0
- package/src/renderer/custom-shader-runtime.ts +309 -0
- package/src/renderer/dither-textures.ts +99 -0
- package/src/renderer/dithering-pass.ts +322 -0
- package/src/renderer/gradient-pass.ts +520 -0
- package/src/renderer/halftone-pass.ts +932 -0
- package/src/renderer/ink-pass.ts +683 -0
- package/src/renderer/live-pass.ts +194 -0
- package/src/renderer/media-pass.ts +187 -0
- package/src/renderer/media-texture.ts +66 -0
- package/src/renderer/particle-grid-pass.ts +389 -0
- package/src/renderer/pass-node-factory.ts +33 -0
- package/src/renderer/pass-node.ts +209 -0
- package/src/renderer/pattern-atlas.ts +97 -0
- package/src/renderer/pattern-pass.ts +552 -0
- package/src/renderer/pipeline-manager.ts +343 -0
- package/src/renderer/pixel-sorting-pass.ts +277 -0
- package/src/renderer/project-clock.ts +57 -0
- package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
- package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
- package/src/renderer/shaders/tsl/noise/common.ts +30 -0
- package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
- package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
- package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
- package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
- package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
- package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
- package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
- package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
- package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
- package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
- package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
- package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
- package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
- package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
- package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
- package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
- package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
- package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
- package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
- package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
- package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
- package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
- package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
- package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
- package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
- package/src/renderer/shaders/tsl/utils/index.ts +47 -0
- package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
- package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
- package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
- package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
- package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
- package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
- package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
- package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
- package/src/renderer/text-pass.ts +176 -0
- package/src/store/asset-store.ts +193 -0
- package/src/store/editor-store.ts +223 -0
- package/src/store/history-store.ts +172 -0
- package/src/store/index.ts +31 -0
- package/src/store/layer-store.ts +675 -0
- package/src/store/timeline-store.ts +572 -0
- package/src/types/assets.d.ts +6 -0
- package/src/types/css.d.ts +21 -0
- package/src/types/editor.ts +357 -0
- package/src/types/react.d.ts +15 -0
- package/src/types/three-tsl.d.ts +146 -0
- package/src/types/three-webgpu.d.ts +51 -0
- package/tsconfig.json +49 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu"
|
|
2
|
+
|
|
3
|
+
export const PATTERN_PRESET_SOURCES = {
|
|
4
|
+
bars: [
|
|
5
|
+
new URL("../../../assets/patterns/bars/1.svg", import.meta.url).toString(),
|
|
6
|
+
new URL("../../../assets/patterns/bars/2.svg", import.meta.url).toString(),
|
|
7
|
+
new URL("../../../assets/patterns/bars/3.svg", import.meta.url).toString(),
|
|
8
|
+
new URL("../../../assets/patterns/bars/4.svg", import.meta.url).toString(),
|
|
9
|
+
new URL("../../../assets/patterns/bars/5.svg", import.meta.url).toString(),
|
|
10
|
+
new URL("../../../assets/patterns/bars/6.svg", import.meta.url).toString(),
|
|
11
|
+
],
|
|
12
|
+
candles: [
|
|
13
|
+
new URL(
|
|
14
|
+
"../../../assets/patterns/candles/1.svg",
|
|
15
|
+
import.meta.url
|
|
16
|
+
).toString(),
|
|
17
|
+
new URL(
|
|
18
|
+
"../../../assets/patterns/candles/2.svg",
|
|
19
|
+
import.meta.url
|
|
20
|
+
).toString(),
|
|
21
|
+
new URL(
|
|
22
|
+
"../../../assets/patterns/candles/3.svg",
|
|
23
|
+
import.meta.url
|
|
24
|
+
).toString(),
|
|
25
|
+
new URL(
|
|
26
|
+
"../../../assets/patterns/candles/4.svg",
|
|
27
|
+
import.meta.url
|
|
28
|
+
).toString(),
|
|
29
|
+
],
|
|
30
|
+
shapes: [
|
|
31
|
+
new URL(
|
|
32
|
+
"../../../assets/patterns/shapes/1.svg",
|
|
33
|
+
import.meta.url
|
|
34
|
+
).toString(),
|
|
35
|
+
new URL(
|
|
36
|
+
"../../../assets/patterns/shapes/2.svg",
|
|
37
|
+
import.meta.url
|
|
38
|
+
).toString(),
|
|
39
|
+
new URL(
|
|
40
|
+
"../../../assets/patterns/shapes/3.svg",
|
|
41
|
+
import.meta.url
|
|
42
|
+
).toString(),
|
|
43
|
+
new URL(
|
|
44
|
+
"../../../assets/patterns/shapes/4.svg",
|
|
45
|
+
import.meta.url
|
|
46
|
+
).toString(),
|
|
47
|
+
new URL(
|
|
48
|
+
"../../../assets/patterns/shapes/5.svg",
|
|
49
|
+
import.meta.url
|
|
50
|
+
).toString(),
|
|
51
|
+
new URL(
|
|
52
|
+
"../../../assets/patterns/shapes/6.svg",
|
|
53
|
+
import.meta.url
|
|
54
|
+
).toString(),
|
|
55
|
+
],
|
|
56
|
+
} as const
|
|
57
|
+
|
|
58
|
+
export type PatternPreset = keyof typeof PATTERN_PRESET_SOURCES
|
|
59
|
+
|
|
60
|
+
function loadSvg(url: string): Promise<HTMLImageElement> {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const image = new Image()
|
|
63
|
+
image.decoding = "async"
|
|
64
|
+
image.onload = () => resolve(image)
|
|
65
|
+
image.onerror = () =>
|
|
66
|
+
reject(new Error(`Unable to load SVG pattern: ${url}`))
|
|
67
|
+
image.src = url
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function buildPatternAtlas(
|
|
72
|
+
preset: PatternPreset,
|
|
73
|
+
cellPx = 16
|
|
74
|
+
): Promise<THREE.CanvasTexture> {
|
|
75
|
+
const urls = PATTERN_PRESET_SOURCES[preset]
|
|
76
|
+
const cellSize = Math.max(4, Math.round(cellPx))
|
|
77
|
+
const images = await Promise.all(urls.map((url) => loadSvg(url)))
|
|
78
|
+
const canvas = document.createElement("canvas")
|
|
79
|
+
canvas.width = urls.length * cellSize
|
|
80
|
+
canvas.height = cellSize
|
|
81
|
+
|
|
82
|
+
const context = canvas.getContext("2d")
|
|
83
|
+
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error("Unable to create 2D context for pattern atlas")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
context.clearRect(0, 0, canvas.width, canvas.height)
|
|
89
|
+
context.fillStyle = "#000"
|
|
90
|
+
context.fillRect(0, 0, canvas.width, canvas.height)
|
|
91
|
+
context.imageSmoothingEnabled = true
|
|
92
|
+
|
|
93
|
+
for (const [index, image] of images.entries()) {
|
|
94
|
+
const aspect = image.naturalWidth / Math.max(image.naturalHeight, 1)
|
|
95
|
+
const drawWidth = aspect >= 1 ? cellSize : cellSize * aspect
|
|
96
|
+
const drawHeight =
|
|
97
|
+
aspect >= 1 ? cellSize / Math.max(aspect, 0.0001) : cellSize
|
|
98
|
+
const x = index * cellSize + (cellSize - drawWidth) * 0.5
|
|
99
|
+
const y = (cellSize - drawHeight) * 0.5
|
|
100
|
+
|
|
101
|
+
context.drawImage(image, x, y, drawWidth, drawHeight)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
|
|
105
|
+
const { data } = imageData
|
|
106
|
+
|
|
107
|
+
for (let index = 0; index < data.length; index += 4) {
|
|
108
|
+
const alpha = data[index + 3] ?? 0
|
|
109
|
+
const luminance = Math.max(
|
|
110
|
+
data[index] ?? 0,
|
|
111
|
+
data[index + 1] ?? 0,
|
|
112
|
+
data[index + 2] ?? 0
|
|
113
|
+
)
|
|
114
|
+
const mask = alpha > 0 && luminance > 32 ? 255 : 0
|
|
115
|
+
data[index] = mask
|
|
116
|
+
data[index + 1] = mask
|
|
117
|
+
data[index + 2] = mask
|
|
118
|
+
data[index + 3] = 255
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
context.putImageData(imageData, 0, 0)
|
|
122
|
+
|
|
123
|
+
const texture = new THREE.CanvasTexture(canvas)
|
|
124
|
+
texture.flipY = false
|
|
125
|
+
texture.generateMipmaps = false
|
|
126
|
+
texture.magFilter = THREE.NearestFilter
|
|
127
|
+
texture.minFilter = THREE.NearestFilter
|
|
128
|
+
texture.wrapS = THREE.ClampToEdgeWrapping
|
|
129
|
+
texture.wrapT = THREE.ClampToEdgeWrapping
|
|
130
|
+
texture.needsUpdate = true
|
|
131
|
+
|
|
132
|
+
return texture
|
|
133
|
+
}
|
|
@@ -0,0 +1,552 @@
|
|
|
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
|
+
buildPatternAtlas,
|
|
20
|
+
type PatternPreset,
|
|
21
|
+
} from "./pattern-atlas"
|
|
22
|
+
import { PassNode } from "./pass-node"
|
|
23
|
+
import type { LayerParameterValues } from "../types/editor"
|
|
24
|
+
|
|
25
|
+
type Node = TSLNode
|
|
26
|
+
type PatternColorMode = "custom" | "monochrome" | "quantized" | "source"
|
|
27
|
+
|
|
28
|
+
function clamp01(value: number): number {
|
|
29
|
+
return Math.max(0, Math.min(1, value))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseCssColorRgb(value: string): [number, number, number] {
|
|
33
|
+
const rgba = value.match(
|
|
34
|
+
/rgba?\s*\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*[\d.]+)?\s*\)/i,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if (rgba) {
|
|
38
|
+
return [
|
|
39
|
+
clamp01(Number.parseFloat(rgba[1] ?? "0") / 255),
|
|
40
|
+
clamp01(Number.parseFloat(rgba[2] ?? "0") / 255),
|
|
41
|
+
clamp01(Number.parseFloat(rgba[3] ?? "0") / 255),
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const hex = value.trim().replace("#", "")
|
|
46
|
+
|
|
47
|
+
if (hex.length === 6) {
|
|
48
|
+
return [
|
|
49
|
+
Number.parseInt(hex.slice(0, 2), 16) / 255,
|
|
50
|
+
Number.parseInt(hex.slice(2, 4), 16) / 255,
|
|
51
|
+
Number.parseInt(hex.slice(4, 6), 16) / 255,
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hex.length === 3) {
|
|
56
|
+
return [
|
|
57
|
+
Number.parseInt(`${hex[0] ?? "0"}${hex[0] ?? "0"}`, 16) / 255,
|
|
58
|
+
Number.parseInt(`${hex[1] ?? "0"}${hex[1] ?? "0"}`, 16) / 255,
|
|
59
|
+
Number.parseInt(`${hex[2] ?? "0"}${hex[2] ?? "0"}`, 16) / 255,
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [1, 1, 1]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export class PatternPass extends PassNode {
|
|
67
|
+
private atlasTexture: THREE.CanvasTexture | null = null
|
|
68
|
+
private atlasTextureNodes: Node[] = []
|
|
69
|
+
private bloomEnabled = false
|
|
70
|
+
private bloomNode: ReturnType<typeof bloom> | null = null
|
|
71
|
+
private readonly bloomIntensityUniform: Node
|
|
72
|
+
private readonly bloomRadiusUniform: Node
|
|
73
|
+
private readonly bloomSoftnessUniform: Node
|
|
74
|
+
private readonly bloomThresholdUniform: Node
|
|
75
|
+
private sourceTextureNodes: Node[] = []
|
|
76
|
+
|
|
77
|
+
private readonly bgOpacityUniform: Node
|
|
78
|
+
private readonly cellSizeUniform: Node
|
|
79
|
+
private readonly colorModeUniform: Node
|
|
80
|
+
private readonly customBgColorUniform: Node
|
|
81
|
+
private readonly customColor1Uniform: Node
|
|
82
|
+
private readonly customColor2Uniform: Node
|
|
83
|
+
private readonly customColor3Uniform: Node
|
|
84
|
+
private readonly customColor4Uniform: Node
|
|
85
|
+
private readonly customColorCountUniform: Node
|
|
86
|
+
private readonly customLuminanceBiasUniform: Node
|
|
87
|
+
private readonly invertUniform: Node
|
|
88
|
+
private readonly logicalHeightUniform: Node
|
|
89
|
+
private readonly logicalWidthUniform: Node
|
|
90
|
+
private readonly monoBlueUniform: Node
|
|
91
|
+
private readonly monoGreenUniform: Node
|
|
92
|
+
private readonly monoRedUniform: Node
|
|
93
|
+
private readonly numPatternsUniform: Node
|
|
94
|
+
private readonly placeholder: THREE.Texture
|
|
95
|
+
|
|
96
|
+
private atlasBuildRequestId = 0
|
|
97
|
+
private atlasPending = false
|
|
98
|
+
private currentCellSize = 12
|
|
99
|
+
private currentPreset: PatternPreset = "bars"
|
|
100
|
+
private needsRefresh = false
|
|
101
|
+
|
|
102
|
+
constructor(layerId: string) {
|
|
103
|
+
super(layerId)
|
|
104
|
+
this.placeholder = new THREE.Texture()
|
|
105
|
+
this.bloomIntensityUniform = uniform(1.25)
|
|
106
|
+
this.bloomRadiusUniform = uniform(6)
|
|
107
|
+
this.bloomSoftnessUniform = uniform(0.35)
|
|
108
|
+
this.bloomThresholdUniform = uniform(0.6)
|
|
109
|
+
this.bgOpacityUniform = uniform(0)
|
|
110
|
+
this.cellSizeUniform = uniform(12)
|
|
111
|
+
this.colorModeUniform = uniform(0)
|
|
112
|
+
this.customColorCountUniform = uniform(4)
|
|
113
|
+
this.customLuminanceBiasUniform = uniform(0)
|
|
114
|
+
this.customBgColorUniform = uniform(new THREE.Vector3(0.96, 0.96, 0.94))
|
|
115
|
+
this.customColor1Uniform = uniform(new THREE.Vector3(0.05, 0.063, 0.078))
|
|
116
|
+
this.customColor2Uniform = uniform(new THREE.Vector3(0.302, 0.314, 0.341))
|
|
117
|
+
this.customColor3Uniform = uniform(new THREE.Vector3(0.588, 0.604, 0.635))
|
|
118
|
+
this.customColor4Uniform = uniform(new THREE.Vector3(0.882, 0.886, 0.871))
|
|
119
|
+
this.invertUniform = uniform(0)
|
|
120
|
+
this.logicalWidthUniform = uniform(1)
|
|
121
|
+
this.logicalHeightUniform = uniform(1)
|
|
122
|
+
this.monoRedUniform = uniform(0.96)
|
|
123
|
+
this.monoGreenUniform = uniform(0.96)
|
|
124
|
+
this.monoBlueUniform = uniform(0.94)
|
|
125
|
+
this.numPatternsUniform = uniform(1)
|
|
126
|
+
this.rebuildAtlas()
|
|
127
|
+
this.rebuildEffectNode()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
override render(
|
|
131
|
+
renderer: THREE.WebGPURenderer,
|
|
132
|
+
inputTexture: THREE.Texture,
|
|
133
|
+
outputTarget: THREE.WebGLRenderTarget,
|
|
134
|
+
time: number,
|
|
135
|
+
delta: number,
|
|
136
|
+
): void {
|
|
137
|
+
for (const sourceTextureNode of this.sourceTextureNodes) {
|
|
138
|
+
sourceTextureNode.value = inputTexture
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (this.atlasTexture) {
|
|
142
|
+
for (const atlasTextureNode of this.atlasTextureNodes) {
|
|
143
|
+
atlasTextureNode.value = this.atlasTexture
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
super.render(renderer, inputTexture, outputTarget, time, delta)
|
|
148
|
+
this.needsRefresh = false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
override updateParams(params: LayerParameterValues): void {
|
|
152
|
+
const nextCellSize =
|
|
153
|
+
typeof params.cellSize === "number" ? Math.max(4, Math.round(params.cellSize)) : 12
|
|
154
|
+
const nextPreset = this.resolvePreset(params.preset)
|
|
155
|
+
const nextColorMode = this.resolveColorMode(params.colorMode)
|
|
156
|
+
const nextBgOpacity =
|
|
157
|
+
typeof params.bgOpacity === "number" ? clamp01(params.bgOpacity) : 0
|
|
158
|
+
const nextCustomColorCount =
|
|
159
|
+
typeof params.customColorCount === "number"
|
|
160
|
+
? Math.min(4, Math.max(2, Math.round(params.customColorCount)))
|
|
161
|
+
: 4
|
|
162
|
+
const nextCustomLuminanceBias =
|
|
163
|
+
typeof params.customLuminanceBias === "number"
|
|
164
|
+
? Math.min(1, Math.max(-1, params.customLuminanceBias))
|
|
165
|
+
: 0
|
|
166
|
+
const nextBloomEnabled = params.bloomEnabled === true
|
|
167
|
+
const nextBloomIntensity =
|
|
168
|
+
typeof params.bloomIntensity === "number" ? Math.max(0, params.bloomIntensity) : 1.25
|
|
169
|
+
const nextBloomThreshold =
|
|
170
|
+
typeof params.bloomThreshold === "number" ? clamp01(params.bloomThreshold) : 0.6
|
|
171
|
+
const nextBloomRadius =
|
|
172
|
+
typeof params.bloomRadius === "number" ? Math.max(0, params.bloomRadius) : 6
|
|
173
|
+
const nextBloomSoftness =
|
|
174
|
+
typeof params.bloomSoftness === "number" ? clamp01(params.bloomSoftness) : 0.35
|
|
175
|
+
const [red, green, blue] = parseCssColorRgb(
|
|
176
|
+
typeof params.monoColor === "string" ? params.monoColor : "#f5f5f0",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
this.bgOpacityUniform.value = nextBgOpacity
|
|
180
|
+
this.bloomIntensityUniform.value = nextBloomIntensity
|
|
181
|
+
this.bloomRadiusUniform.value = nextBloomRadius
|
|
182
|
+
this.bloomSoftnessUniform.value = nextBloomSoftness
|
|
183
|
+
this.bloomThresholdUniform.value = nextBloomThreshold
|
|
184
|
+
this.cellSizeUniform.value = nextCellSize
|
|
185
|
+
this.colorModeUniform.value = this.getColorModeValue(nextColorMode)
|
|
186
|
+
this.customColorCountUniform.value = nextCustomColorCount
|
|
187
|
+
this.customLuminanceBiasUniform.value = nextCustomLuminanceBias
|
|
188
|
+
this.invertUniform.value = params.invert === true ? 1 : 0
|
|
189
|
+
this.monoRedUniform.value = red
|
|
190
|
+
this.monoGreenUniform.value = green
|
|
191
|
+
this.monoBlueUniform.value = blue
|
|
192
|
+
this.setCustomColorUniform(
|
|
193
|
+
this.customBgColorUniform,
|
|
194
|
+
typeof params.customBgColor === "string" ? params.customBgColor : "#F5F5F0",
|
|
195
|
+
)
|
|
196
|
+
this.setCustomColorUniform(
|
|
197
|
+
this.customColor1Uniform,
|
|
198
|
+
typeof params.customColor1 === "string" ? params.customColor1 : "#0d1014",
|
|
199
|
+
)
|
|
200
|
+
this.setCustomColorUniform(
|
|
201
|
+
this.customColor2Uniform,
|
|
202
|
+
typeof params.customColor2 === "string" ? params.customColor2 : "#4d5057",
|
|
203
|
+
)
|
|
204
|
+
this.setCustomColorUniform(
|
|
205
|
+
this.customColor3Uniform,
|
|
206
|
+
typeof params.customColor3 === "string" ? params.customColor3 : "#969aa2",
|
|
207
|
+
)
|
|
208
|
+
this.setCustomColorUniform(
|
|
209
|
+
this.customColor4Uniform,
|
|
210
|
+
typeof params.customColor4 === "string" ? params.customColor4 : "#e1e2de",
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const needsAtlasRebuild =
|
|
214
|
+
nextCellSize !== this.currentCellSize || nextPreset !== this.currentPreset
|
|
215
|
+
|
|
216
|
+
this.currentCellSize = nextCellSize
|
|
217
|
+
this.currentPreset = nextPreset
|
|
218
|
+
|
|
219
|
+
if (nextBloomEnabled !== this.bloomEnabled) {
|
|
220
|
+
this.bloomEnabled = nextBloomEnabled
|
|
221
|
+
this.rebuildEffectNode()
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this.bloomNode) {
|
|
226
|
+
this.bloomNode.strength.value = nextBloomIntensity
|
|
227
|
+
this.bloomNode.radius.value = this.normalizeBloomRadius(nextBloomRadius)
|
|
228
|
+
this.bloomNode.threshold.value = nextBloomThreshold
|
|
229
|
+
this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(nextBloomSoftness)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (needsAtlasRebuild) {
|
|
233
|
+
this.rebuildAtlas()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
override updateLogicalSize(width: number, height: number): void {
|
|
238
|
+
this.logicalWidthUniform.value = Math.max(1, width)
|
|
239
|
+
this.logicalHeightUniform.value = Math.max(1, height)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
override needsContinuousRender(): boolean {
|
|
243
|
+
return this.atlasPending || this.needsRefresh
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
override dispose(): void {
|
|
247
|
+
this.disposeBloomNode()
|
|
248
|
+
this.placeholder.dispose()
|
|
249
|
+
this.atlasTexture?.dispose()
|
|
250
|
+
super.dispose()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
protected override buildEffectNode(): Node {
|
|
254
|
+
if (!(this.cellSizeUniform && this.numPatternsUniform && this.placeholder)) {
|
|
255
|
+
return this.inputNode
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.disposeBloomNode()
|
|
259
|
+
this.bloomNode = null
|
|
260
|
+
this.sourceTextureNodes = []
|
|
261
|
+
this.atlasTextureNodes = []
|
|
262
|
+
|
|
263
|
+
const renderTargetUv = vec2(uv().x, float(1).sub(uv().y))
|
|
264
|
+
const logicalScreenSize = vec2(this.logicalWidthUniform, this.logicalHeightUniform)
|
|
265
|
+
const normalizedCellSize = vec2(this.cellSizeUniform, this.cellSizeUniform).div(
|
|
266
|
+
logicalScreenSize,
|
|
267
|
+
)
|
|
268
|
+
const quantizeLevels = vec3(float(7), float(7), float(3))
|
|
269
|
+
const inverseQuantizeLevels = vec3(float(1 / 7), float(1 / 7), float(1 / 3))
|
|
270
|
+
const customBgVec = vec3(
|
|
271
|
+
float(this.customBgColorUniform.x),
|
|
272
|
+
float(this.customBgColorUniform.y),
|
|
273
|
+
float(this.customBgColorUniform.z),
|
|
274
|
+
)
|
|
275
|
+
const customColor1Vec = vec3(
|
|
276
|
+
float(this.customColor1Uniform.x),
|
|
277
|
+
float(this.customColor1Uniform.y),
|
|
278
|
+
float(this.customColor1Uniform.z),
|
|
279
|
+
)
|
|
280
|
+
const customColor2Vec = vec3(
|
|
281
|
+
float(this.customColor2Uniform.x),
|
|
282
|
+
float(this.customColor2Uniform.y),
|
|
283
|
+
float(this.customColor2Uniform.z),
|
|
284
|
+
)
|
|
285
|
+
const customColor3Vec = vec3(
|
|
286
|
+
float(this.customColor3Uniform.x),
|
|
287
|
+
float(this.customColor3Uniform.y),
|
|
288
|
+
float(this.customColor3Uniform.z),
|
|
289
|
+
)
|
|
290
|
+
const customColor4Vec = vec3(
|
|
291
|
+
float(this.customColor4Uniform.x),
|
|
292
|
+
float(this.customColor4Uniform.y),
|
|
293
|
+
float(this.customColor4Uniform.z),
|
|
294
|
+
)
|
|
295
|
+
const customColorCount = clamp(
|
|
296
|
+
float(this.customColorCountUniform),
|
|
297
|
+
float(2),
|
|
298
|
+
float(4),
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
const samplePattern = (sampleUv: Node) => {
|
|
302
|
+
const safeUv = clamp(sampleUv, vec2(float(0), float(0)), vec2(float(1), float(1)))
|
|
303
|
+
const screenPixel = floor(safeUv.mul(logicalScreenSize))
|
|
304
|
+
const tileCenterUv = floor(safeUv.div(normalizedCellSize))
|
|
305
|
+
.add(vec2(0.5, 0.5))
|
|
306
|
+
.mul(normalizedCellSize)
|
|
307
|
+
const localCellPixel = vec2(
|
|
308
|
+
mod(screenPixel.x, this.cellSizeUniform),
|
|
309
|
+
mod(screenPixel.y, this.cellSizeUniform),
|
|
310
|
+
)
|
|
311
|
+
const sampledColor = this.trackSourceTextureNode(tileCenterUv)
|
|
312
|
+
const sourceColor = vec3(
|
|
313
|
+
float(sampledColor.r),
|
|
314
|
+
float(sampledColor.g),
|
|
315
|
+
float(sampledColor.b),
|
|
316
|
+
)
|
|
317
|
+
const luma = float(sampledColor.r)
|
|
318
|
+
.mul(float(0.299))
|
|
319
|
+
.add(float(sampledColor.g).mul(float(0.45)))
|
|
320
|
+
.add(float(sampledColor.b).mul(float(0.114)))
|
|
321
|
+
const adjustedLuma = select(
|
|
322
|
+
this.invertUniform.greaterThan(float(0.5)),
|
|
323
|
+
float(1).sub(luma),
|
|
324
|
+
luma,
|
|
325
|
+
)
|
|
326
|
+
const patternIndex = floor(
|
|
327
|
+
clamp(
|
|
328
|
+
adjustedLuma.mul(this.numPatternsUniform.sub(float(1))),
|
|
329
|
+
float(0),
|
|
330
|
+
this.numPatternsUniform.sub(float(1)),
|
|
331
|
+
),
|
|
332
|
+
)
|
|
333
|
+
const atlasUv = vec2(
|
|
334
|
+
patternIndex
|
|
335
|
+
.mul(this.cellSizeUniform)
|
|
336
|
+
.add(localCellPixel.x)
|
|
337
|
+
.add(float(0.5))
|
|
338
|
+
.div(this.numPatternsUniform.mul(this.cellSizeUniform)),
|
|
339
|
+
localCellPixel.y.add(float(0.5)).div(this.cellSizeUniform),
|
|
340
|
+
)
|
|
341
|
+
const patternMask = float(this.trackAtlasTextureNode(atlasUv).r)
|
|
342
|
+
const quantized = floor(sourceColor.mul(quantizeLevels).add(vec3(0.5, 0.5, 0.5))).mul(
|
|
343
|
+
inverseQuantizeLevels,
|
|
344
|
+
)
|
|
345
|
+
const quantizedLuma = float(quantized.x)
|
|
346
|
+
.mul(float(0.299))
|
|
347
|
+
.add(float(quantized.y).mul(float(0.45)))
|
|
348
|
+
.add(float(quantized.z).mul(float(0.114)))
|
|
349
|
+
const quantizedColor = clamp(
|
|
350
|
+
mix(vec3(quantizedLuma, quantizedLuma, quantizedLuma), quantized, float(1.2)),
|
|
351
|
+
vec3(float(0), float(0), float(0)),
|
|
352
|
+
vec3(float(1), float(1), float(1)),
|
|
353
|
+
)
|
|
354
|
+
const monochromeColor = vec3(
|
|
355
|
+
this.monoRedUniform,
|
|
356
|
+
this.monoGreenUniform,
|
|
357
|
+
this.monoBlueUniform,
|
|
358
|
+
).mul(adjustedLuma)
|
|
359
|
+
const customLuminance = clamp(
|
|
360
|
+
adjustedLuma.add(float(this.customLuminanceBiasUniform).mul(float(0.35))),
|
|
361
|
+
float(0),
|
|
362
|
+
float(1),
|
|
363
|
+
)
|
|
364
|
+
const customColor = select(
|
|
365
|
+
customColorCount.lessThan(float(2.5)),
|
|
366
|
+
select(
|
|
367
|
+
customLuminance.lessThan(float(0.5)),
|
|
368
|
+
customColor1Vec,
|
|
369
|
+
customColor2Vec,
|
|
370
|
+
),
|
|
371
|
+
select(
|
|
372
|
+
customColorCount.lessThan(float(3.5)),
|
|
373
|
+
select(
|
|
374
|
+
customLuminance.lessThan(float(1 / 3)),
|
|
375
|
+
customColor1Vec,
|
|
376
|
+
select(
|
|
377
|
+
customLuminance.lessThan(float(2 / 3)),
|
|
378
|
+
customColor2Vec,
|
|
379
|
+
customColor3Vec,
|
|
380
|
+
),
|
|
381
|
+
),
|
|
382
|
+
select(
|
|
383
|
+
customLuminance.lessThan(float(0.25)),
|
|
384
|
+
customColor1Vec,
|
|
385
|
+
select(
|
|
386
|
+
customLuminance.lessThan(float(0.5)),
|
|
387
|
+
customColor2Vec,
|
|
388
|
+
select(
|
|
389
|
+
customLuminance.lessThan(float(0.75)),
|
|
390
|
+
customColor3Vec,
|
|
391
|
+
customColor4Vec,
|
|
392
|
+
),
|
|
393
|
+
),
|
|
394
|
+
),
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
const patternColor = select(
|
|
398
|
+
this.colorModeUniform.lessThan(float(0.5)),
|
|
399
|
+
sourceColor,
|
|
400
|
+
select(
|
|
401
|
+
this.colorModeUniform.lessThan(float(1.5)),
|
|
402
|
+
quantizedColor,
|
|
403
|
+
select(
|
|
404
|
+
this.colorModeUniform.lessThan(float(2.5)),
|
|
405
|
+
monochromeColor,
|
|
406
|
+
customColor,
|
|
407
|
+
),
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
const sourceBackground = sourceColor.mul(this.bgOpacityUniform)
|
|
411
|
+
const backgroundColor = select(
|
|
412
|
+
this.colorModeUniform.lessThan(float(0.5)),
|
|
413
|
+
sourceBackground,
|
|
414
|
+
select(
|
|
415
|
+
this.colorModeUniform.lessThan(float(2.5)),
|
|
416
|
+
vec3(float(0), float(0), float(0)),
|
|
417
|
+
customBgVec,
|
|
418
|
+
),
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
return vec4(mix(backgroundColor, patternColor, patternMask), float(1))
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const baseSample = samplePattern(renderTargetUv)
|
|
425
|
+
|
|
426
|
+
if (!this.bloomEnabled) {
|
|
427
|
+
return baseSample
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
this.bloomNode = bloom(
|
|
431
|
+
vec4(baseSample.rgb, float(1)),
|
|
432
|
+
this.bloomIntensityUniform.value as number,
|
|
433
|
+
this.normalizeBloomRadius(this.bloomRadiusUniform.value as number),
|
|
434
|
+
this.bloomThresholdUniform.value as number,
|
|
435
|
+
)
|
|
436
|
+
this.bloomNode.smoothWidth.value = this.normalizeBloomSoftness(
|
|
437
|
+
this.bloomSoftnessUniform.value as number,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
return vec4(
|
|
441
|
+
clamp(
|
|
442
|
+
baseSample.rgb.add(this.getBloomTextureNode().rgb),
|
|
443
|
+
vec3(float(0), float(0), float(0)),
|
|
444
|
+
vec3(float(1), float(1), float(1)),
|
|
445
|
+
),
|
|
446
|
+
float(1),
|
|
447
|
+
)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private getColorModeValue(colorMode: PatternColorMode): number {
|
|
451
|
+
switch (colorMode) {
|
|
452
|
+
case "quantized":
|
|
453
|
+
return 1
|
|
454
|
+
case "monochrome":
|
|
455
|
+
return 2
|
|
456
|
+
case "custom":
|
|
457
|
+
return 3
|
|
458
|
+
default:
|
|
459
|
+
return 0
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private rebuildAtlas(): void {
|
|
464
|
+
const requestId = ++this.atlasBuildRequestId
|
|
465
|
+
this.atlasPending = true
|
|
466
|
+
|
|
467
|
+
void buildPatternAtlas(this.currentPreset, this.currentCellSize)
|
|
468
|
+
.then((atlasTexture) => {
|
|
469
|
+
if (requestId !== this.atlasBuildRequestId) {
|
|
470
|
+
atlasTexture.dispose()
|
|
471
|
+
return
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.atlasTexture?.dispose()
|
|
475
|
+
this.atlasTexture = atlasTexture
|
|
476
|
+
this.numPatternsUniform.value = atlasTexture.image.width / this.currentCellSize
|
|
477
|
+
this.atlasPending = false
|
|
478
|
+
this.needsRefresh = true
|
|
479
|
+
this.rebuildEffectNode()
|
|
480
|
+
})
|
|
481
|
+
.catch(() => {
|
|
482
|
+
if (requestId !== this.atlasBuildRequestId) {
|
|
483
|
+
return
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
this.atlasPending = false
|
|
487
|
+
this.needsRefresh = true
|
|
488
|
+
})
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private resolveColorMode(value: unknown): PatternColorMode {
|
|
492
|
+
return value === "quantized" || value === "monochrome" || value === "custom"
|
|
493
|
+
? value
|
|
494
|
+
: "source"
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private resolvePreset(value: unknown): PatternPreset {
|
|
498
|
+
return value === "candles" || value === "shapes" ? value : "bars"
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private normalizeBloomRadius(value: number): number {
|
|
502
|
+
return clamp01(value / 24)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private normalizeBloomSoftness(value: number): number {
|
|
506
|
+
return Math.max(0.001, value * 0.25)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private disposeBloomNode(): void {
|
|
510
|
+
;(this.bloomNode as { dispose?: () => void } | null)?.dispose?.()
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private getBloomTextureNode(): Node {
|
|
514
|
+
const bloomNode = this.bloomNode as
|
|
515
|
+
| ({
|
|
516
|
+
getTexture?: () => Node
|
|
517
|
+
getTextureNode?: () => Node
|
|
518
|
+
} & object)
|
|
519
|
+
| null
|
|
520
|
+
|
|
521
|
+
if (!bloomNode) {
|
|
522
|
+
throw new Error("Bloom node is not initialized")
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if ("getTextureNode" in bloomNode && typeof bloomNode.getTextureNode === "function") {
|
|
526
|
+
return bloomNode.getTextureNode()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if ("getTexture" in bloomNode && typeof bloomNode.getTexture === "function") {
|
|
530
|
+
return bloomNode.getTexture()
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
throw new Error("Bloom node does not expose a texture getter")
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private trackAtlasTextureNode(uvNode: Node): Node {
|
|
537
|
+
const atlasTextureNode = tslTexture(this.atlasTexture ?? new THREE.Texture(), uvNode)
|
|
538
|
+
this.atlasTextureNodes.push(atlasTextureNode)
|
|
539
|
+
return atlasTextureNode
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private trackSourceTextureNode(uvNode: Node): Node {
|
|
543
|
+
const sourceTextureNode = tslTexture(this.placeholder, uvNode)
|
|
544
|
+
this.sourceTextureNodes.push(sourceTextureNode)
|
|
545
|
+
return sourceTextureNode
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private setCustomColorUniform(target: Node, value: string): void {
|
|
549
|
+
const [r, g, b] = parseCssColorRgb(value)
|
|
550
|
+
;(target.value as THREE.Vector3).set(r, g, b)
|
|
551
|
+
}
|
|
552
|
+
}
|