@stevejtrettel/shader-sandbox 0.1.2 → 0.1.4

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 (113) hide show
  1. package/README.md +259 -235
  2. package/bin/cli.js +106 -14
  3. package/dist-lib/app/App.d.ts +143 -15
  4. package/dist-lib/app/App.d.ts.map +1 -1
  5. package/dist-lib/app/App.js +1343 -108
  6. package/dist-lib/app/app.css +349 -24
  7. package/dist-lib/app/types.d.ts +48 -5
  8. package/dist-lib/app/types.d.ts.map +1 -1
  9. package/dist-lib/editor/EditorPanel.d.ts +2 -2
  10. package/dist-lib/editor/EditorPanel.d.ts.map +1 -1
  11. package/dist-lib/editor/EditorPanel.js +1 -1
  12. package/dist-lib/editor/editor-panel.css +55 -32
  13. package/dist-lib/editor/prism-editor.css +16 -16
  14. package/dist-lib/embed.js +1 -1
  15. package/dist-lib/engine/{ShadertoyEngine.d.ts → ShaderEngine.d.ts} +134 -10
  16. package/dist-lib/engine/ShaderEngine.d.ts.map +1 -0
  17. package/dist-lib/engine/ShaderEngine.js +1523 -0
  18. package/dist-lib/engine/glHelpers.d.ts +24 -0
  19. package/dist-lib/engine/glHelpers.d.ts.map +1 -1
  20. package/dist-lib/engine/glHelpers.js +88 -0
  21. package/dist-lib/engine/std140.d.ts +47 -0
  22. package/dist-lib/engine/std140.d.ts.map +1 -0
  23. package/dist-lib/engine/std140.js +119 -0
  24. package/dist-lib/engine/types.d.ts +55 -5
  25. package/dist-lib/engine/types.d.ts.map +1 -1
  26. package/dist-lib/engine/types.js +1 -1
  27. package/dist-lib/index.d.ts +4 -3
  28. package/dist-lib/index.d.ts.map +1 -1
  29. package/dist-lib/index.js +2 -1
  30. package/dist-lib/layouts/SplitLayout.d.ts +2 -1
  31. package/dist-lib/layouts/SplitLayout.d.ts.map +1 -1
  32. package/dist-lib/layouts/SplitLayout.js +3 -0
  33. package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -1
  34. package/dist-lib/layouts/UILayout.d.ts +55 -0
  35. package/dist-lib/layouts/UILayout.d.ts.map +1 -0
  36. package/dist-lib/layouts/UILayout.js +147 -0
  37. package/dist-lib/layouts/default.css +2 -2
  38. package/dist-lib/layouts/index.d.ts +11 -1
  39. package/dist-lib/layouts/index.d.ts.map +1 -1
  40. package/dist-lib/layouts/index.js +17 -1
  41. package/dist-lib/layouts/split.css +33 -31
  42. package/dist-lib/layouts/tabbed.css +127 -74
  43. package/dist-lib/layouts/types.d.ts +14 -3
  44. package/dist-lib/layouts/types.d.ts.map +1 -1
  45. package/dist-lib/main.js +33 -0
  46. package/dist-lib/project/configHelpers.d.ts +45 -0
  47. package/dist-lib/project/configHelpers.d.ts.map +1 -0
  48. package/dist-lib/project/configHelpers.js +196 -0
  49. package/dist-lib/project/generatedLoader.d.ts +2 -2
  50. package/dist-lib/project/generatedLoader.d.ts.map +1 -1
  51. package/dist-lib/project/generatedLoader.js +23 -5
  52. package/dist-lib/project/loadProject.d.ts +6 -6
  53. package/dist-lib/project/loadProject.d.ts.map +1 -1
  54. package/dist-lib/project/loadProject.js +396 -144
  55. package/dist-lib/project/loaderHelper.d.ts +4 -4
  56. package/dist-lib/project/loaderHelper.d.ts.map +1 -1
  57. package/dist-lib/project/loaderHelper.js +278 -116
  58. package/dist-lib/project/types.d.ts +292 -13
  59. package/dist-lib/project/types.d.ts.map +1 -1
  60. package/dist-lib/project/types.js +13 -1
  61. package/dist-lib/styles/base.css +5 -1
  62. package/dist-lib/uniforms/UniformControls.d.ts +60 -0
  63. package/dist-lib/uniforms/UniformControls.d.ts.map +1 -0
  64. package/dist-lib/uniforms/UniformControls.js +518 -0
  65. package/dist-lib/uniforms/UniformStore.d.ts +74 -0
  66. package/dist-lib/uniforms/UniformStore.d.ts.map +1 -0
  67. package/dist-lib/uniforms/UniformStore.js +145 -0
  68. package/dist-lib/uniforms/UniformsPanel.d.ts +53 -0
  69. package/dist-lib/uniforms/UniformsPanel.d.ts.map +1 -0
  70. package/dist-lib/uniforms/UniformsPanel.js +124 -0
  71. package/dist-lib/uniforms/index.d.ts +11 -0
  72. package/dist-lib/uniforms/index.d.ts.map +1 -0
  73. package/dist-lib/uniforms/index.js +8 -0
  74. package/package.json +16 -1
  75. package/src/app/App.ts +1469 -126
  76. package/src/app/app.css +349 -24
  77. package/src/app/types.ts +53 -5
  78. package/src/editor/EditorPanel.ts +5 -5
  79. package/src/editor/editor-panel.css +55 -32
  80. package/src/editor/prism-editor.css +16 -16
  81. package/src/embed.ts +1 -1
  82. package/src/engine/ShaderEngine.ts +1934 -0
  83. package/src/engine/glHelpers.ts +117 -0
  84. package/src/engine/std140.ts +136 -0
  85. package/src/engine/types.ts +69 -5
  86. package/src/index.ts +4 -3
  87. package/src/layouts/SplitLayout.ts +8 -3
  88. package/src/layouts/TabbedLayout.ts +3 -3
  89. package/src/layouts/UILayout.ts +185 -0
  90. package/src/layouts/default.css +2 -2
  91. package/src/layouts/index.ts +20 -1
  92. package/src/layouts/split.css +33 -31
  93. package/src/layouts/tabbed.css +127 -74
  94. package/src/layouts/types.ts +19 -3
  95. package/src/layouts/ui.css +289 -0
  96. package/src/main.ts +39 -1
  97. package/src/project/configHelpers.ts +225 -0
  98. package/src/project/generatedLoader.ts +27 -6
  99. package/src/project/loadProject.ts +459 -173
  100. package/src/project/loaderHelper.ts +377 -130
  101. package/src/project/types.ts +360 -14
  102. package/src/styles/base.css +5 -1
  103. package/src/styles/theme.css +292 -0
  104. package/src/uniforms/UniformControls.ts +660 -0
  105. package/src/uniforms/UniformStore.ts +166 -0
  106. package/src/uniforms/UniformsPanel.ts +163 -0
  107. package/src/uniforms/index.ts +13 -0
  108. package/src/uniforms/uniform-controls.css +342 -0
  109. package/src/uniforms/uniforms-panel.css +277 -0
  110. package/templates/shaders/example-buffer/config.json +1 -0
  111. package/dist-lib/engine/ShadertoyEngine.d.ts.map +0 -1
  112. package/dist-lib/engine/ShadertoyEngine.js +0 -704
  113. package/src/engine/ShadertoyEngine.ts +0 -929
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Shared helpers for config loading.
3
+ * Used by both the Node/CLI loader (loadProject.ts) and
4
+ * the browser/Vite loader (loaderHelper.ts).
5
+ */
6
+ /**
7
+ * Type guard for PassName.
8
+ */
9
+ export function isPassName(s) {
10
+ return s === 'Image' || s === 'BufferA' || s === 'BufferB' || s === 'BufferC' || s === 'BufferD';
11
+ }
12
+ /**
13
+ * Get default source file name for a pass.
14
+ */
15
+ export function defaultSourceForPass(name) {
16
+ switch (name) {
17
+ case 'Image':
18
+ return 'image.glsl';
19
+ case 'BufferA':
20
+ return 'bufferA.glsl';
21
+ case 'BufferB':
22
+ return 'bufferB.glsl';
23
+ case 'BufferC':
24
+ return 'bufferC.glsl';
25
+ case 'BufferD':
26
+ return 'bufferD.glsl';
27
+ }
28
+ }
29
+ /**
30
+ * Parse a channel value (string shorthand or object) into normalized ChannelJSONObject.
31
+ *
32
+ * String shortcuts:
33
+ * - "BufferA", "BufferB", etc. → buffer reference
34
+ * - "keyboard" → keyboard input
35
+ * - "audio" → microphone audio input
36
+ * - "webcam" → webcam video input
37
+ * - "photo.jpg" (with extension) → texture file
38
+ */
39
+ export function parseChannelValue(value) {
40
+ if (typeof value === 'string') {
41
+ if (isPassName(value)) {
42
+ return { buffer: value };
43
+ }
44
+ if (value === 'keyboard') {
45
+ return { keyboard: true };
46
+ }
47
+ if (value === 'audio') {
48
+ return { audio: true };
49
+ }
50
+ if (value === 'webcam') {
51
+ return { webcam: true };
52
+ }
53
+ // Assume texture (file path)
54
+ return { texture: value };
55
+ }
56
+ // Already an object
57
+ return value;
58
+ }
59
+ /** The ordered list of pass names for iteration. */
60
+ export const PASS_ORDER = ['Image', 'BufferA', 'BufferB', 'BufferC', 'BufferD'];
61
+ /** The four buffer pass names (excludes Image). */
62
+ export const BUFFER_PASS_NAMES = ['BufferA', 'BufferB', 'BufferC', 'BufferD'];
63
+ /** The four channel keys. */
64
+ export const CHANNEL_KEYS = ['iChannel0', 'iChannel1', 'iChannel2', 'iChannel3'];
65
+ /** Default layout for projects. */
66
+ export const DEFAULT_LAYOUT = 'default';
67
+ /** Default controls setting. */
68
+ export const DEFAULT_CONTROLS = false;
69
+ /** Default theme. */
70
+ export const DEFAULT_THEME = 'light';
71
+ // =============================================================================
72
+ // Config Validation
73
+ // =============================================================================
74
+ /** Built-in uniform names that cannot be used as custom uniform names. */
75
+ const RESERVED_UNIFORM_NAMES = new Set([
76
+ 'iResolution', 'iTime', 'iTimeDelta', 'iFrame', 'iMouse',
77
+ 'iDate', 'iFrameRate', 'iChannelResolution',
78
+ 'iChannel0', 'iChannel1', 'iChannel2', 'iChannel3',
79
+ 'iTouchCount', 'iTouch0', 'iTouch1', 'iTouch2',
80
+ 'iPinch', 'iPinchDelta', 'iPinchCenter',
81
+ ]);
82
+ const GLSL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
83
+ const GLSL_RESERVED_WORDS = new Set([
84
+ 'attribute', 'const', 'uniform', 'varying', 'break', 'continue',
85
+ 'do', 'for', 'while', 'if', 'else', 'in', 'out', 'inout',
86
+ 'float', 'int', 'void', 'bool', 'true', 'false',
87
+ 'discard', 'return', 'mat2', 'mat3', 'mat4',
88
+ 'vec2', 'vec3', 'vec4', 'ivec2', 'ivec3', 'ivec4',
89
+ 'bvec2', 'bvec3', 'bvec4', 'sampler2D', 'samplerCube',
90
+ 'struct', 'precision', 'highp', 'mediump', 'lowp',
91
+ 'layout', 'centroid', 'flat', 'smooth', 'noperspective',
92
+ 'switch', 'case', 'default',
93
+ ]);
94
+ /** Check if a string is a valid GLSL identifier (not a reserved word). */
95
+ export function isValidGLSLIdentifier(name) {
96
+ return GLSL_IDENTIFIER_RE.test(name) && !GLSL_RESERVED_WORDS.has(name);
97
+ }
98
+ const VALID_LAYOUTS = new Set(['fullscreen', 'default', 'split', 'tabbed']);
99
+ const VALID_THEMES = new Set(['light', 'dark', 'system']);
100
+ const VALID_TOP_LEVEL_KEYS = new Set([
101
+ 'mode', 'title', 'author', 'description', 'layout', 'theme', 'controls',
102
+ 'common', 'startPaused', 'pixelRatio', 'uniforms', 'buffers', 'textures',
103
+ 'Image', 'BufferA', 'BufferB', 'BufferC', 'BufferD',
104
+ ]);
105
+ const VALID_PASS_KEYS = new Set(['source', 'iChannel0', 'iChannel1', 'iChannel2', 'iChannel3']);
106
+ const SPECIAL_TEXTURE_SOURCES = new Set(['keyboard', 'audio', 'webcam']);
107
+ /**
108
+ * Validate a project config and throw on errors.
109
+ * Logs warnings for non-fatal issues.
110
+ */
111
+ export function validateConfig(config, root) {
112
+ const warnings = [];
113
+ const errors = [];
114
+ // Warn on unknown top-level keys
115
+ for (const key of Object.keys(config)) {
116
+ if (!VALID_TOP_LEVEL_KEYS.has(key)) {
117
+ warnings.push(`Unknown config key '${key}'`);
118
+ }
119
+ }
120
+ // Validate layout
121
+ if (config.layout !== undefined && !VALID_LAYOUTS.has(config.layout)) {
122
+ errors.push(`Invalid layout '${config.layout}'. Expected one of: ${[...VALID_LAYOUTS].join(', ')}`);
123
+ }
124
+ // Validate theme
125
+ if (config.theme !== undefined && !VALID_THEMES.has(config.theme)) {
126
+ errors.push(`Invalid theme '${config.theme}'. Expected one of: ${[...VALID_THEMES].join(', ')}`);
127
+ }
128
+ // Validate uniform names
129
+ if (config.uniforms && typeof config.uniforms === 'object') {
130
+ for (const name of Object.keys(config.uniforms)) {
131
+ if (RESERVED_UNIFORM_NAMES.has(name)) {
132
+ errors.push(`Uniform name '${name}' is reserved (built-in uniform)`);
133
+ }
134
+ if (!isValidGLSLIdentifier(name)) {
135
+ errors.push(`Uniform name '${name}' is not a valid GLSL identifier`);
136
+ }
137
+ }
138
+ }
139
+ // Validate buffer names
140
+ const bufferNames = new Set();
141
+ if (config.buffers) {
142
+ const names = Array.isArray(config.buffers) ? config.buffers : Object.keys(config.buffers);
143
+ for (const name of names) {
144
+ if (typeof name !== 'string') {
145
+ errors.push(`Buffer name must be a string, got ${typeof name}`);
146
+ continue;
147
+ }
148
+ if (!isValidGLSLIdentifier(name)) {
149
+ errors.push(`Buffer name '${name}' is not a valid GLSL identifier`);
150
+ }
151
+ bufferNames.add(name);
152
+ }
153
+ }
154
+ // Validate texture names and sources
155
+ if (config.textures && typeof config.textures === 'object') {
156
+ for (const [name, value] of Object.entries(config.textures)) {
157
+ if (!isValidGLSLIdentifier(name)) {
158
+ errors.push(`Texture name '${name}' is not a valid GLSL identifier`);
159
+ }
160
+ if (bufferNames.has(name)) {
161
+ errors.push(`Texture name '${name}' collides with a buffer name`);
162
+ }
163
+ if (typeof value !== 'string') {
164
+ errors.push(`Texture source for '${name}' must be a string`);
165
+ }
166
+ else if (!SPECIAL_TEXTURE_SOURCES.has(value) && !/\.\w+$/.test(value) && !isValidGLSLIdentifier(value)) {
167
+ errors.push(`Invalid texture source '${value}' for '${name}'. Expected a file path with extension, a script texture name, or one of: ${[...SPECIAL_TEXTURE_SOURCES].join(', ')}`);
168
+ }
169
+ }
170
+ }
171
+ // Validate pass configs
172
+ for (const passName of PASS_ORDER) {
173
+ const passConfig = config[passName];
174
+ if (!passConfig || typeof passConfig !== 'object')
175
+ continue;
176
+ for (const key of Object.keys(passConfig)) {
177
+ if (!VALID_PASS_KEYS.has(key)) {
178
+ warnings.push(`Unknown key '${key}' in pass '${passName}'`);
179
+ }
180
+ }
181
+ // Check channel buffer references
182
+ for (const chKey of CHANNEL_KEYS) {
183
+ const val = passConfig[chKey];
184
+ if (!val)
185
+ continue;
186
+ if (typeof val === 'string' && isPassName(val) && val !== 'Image' && !config[val]) {
187
+ warnings.push(`${passName}.${chKey} references '${val}' but no ${val} pass is configured`);
188
+ }
189
+ }
190
+ }
191
+ for (const w of warnings)
192
+ console.warn(`[config] ${root}: ${w}`);
193
+ if (errors.length > 0) {
194
+ throw new Error(`Config validation failed for '${root}':\n${errors.map(e => ` - ${e}`).join('\n')}`);
195
+ }
196
+ }
@@ -1,3 +1,3 @@
1
- export declare const DEMO_NAME = "course/day5/torus-analytical";
2
- export declare function loadDemoProject(): Promise<import("./types").ShadertoyProject>;
1
+ export declare const DEMO_NAME = "demos/examples/ubo-dynamic";
2
+ export declare function loadDemoProject(): Promise<import("./types").ShaderProject>;
3
3
  //# sourceMappingURL=generatedLoader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generatedLoader.d.ts","sourceRoot":"","sources":["../../src/project/generatedLoader.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,SAAS,iCAAiC,CAAC;AAExD,wBAAsB,eAAe,gDAgBpC"}
1
+ {"version":3,"file":"generatedLoader.d.ts","sourceRoot":"","sources":["../../src/project/generatedLoader.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,SAAS,+BAA+B,CAAC;AAatD,wBAAsB,eAAe,6CA0BpC"}
@@ -1,17 +1,35 @@
1
1
  // Auto-generated - DO NOT EDIT
2
2
  import { loadDemo } from './loaderHelper';
3
- export const DEMO_NAME = 'course/day5/torus-analytical';
3
+ export const DEMO_NAME = 'demos/examples/ubo-dynamic';
4
+ // Transform glob keys from "/path" to "./path" format
5
+ function transformKeys(files) {
6
+ const result = {};
7
+ for (const [key, value] of Object.entries(files)) {
8
+ // Convert /demos/... to ./demos/...
9
+ const newKey = key.startsWith('/') ? '.' + key : key;
10
+ result[newKey] = value;
11
+ }
12
+ return result;
13
+ }
4
14
  export async function loadDemoProject() {
5
- const glslFiles = import.meta.glob('/demos/course/day5/torus-analytical/**/*.glsl', {
15
+ // Use root-relative paths for globs (works from any file location)
16
+ const glslFilesRaw = import.meta.glob('/demos/examples/ubo-dynamic/**/*.glsl', {
6
17
  query: '?raw',
7
18
  import: 'default',
8
19
  });
9
- const jsonFiles = import.meta.glob('/demos/course/day5/torus-analytical/**/*.json', {
20
+ const jsonFilesRaw = import.meta.glob('/demos/examples/ubo-dynamic/**/*.json', {
10
21
  import: 'default',
11
22
  });
12
- const imageFiles = import.meta.glob('/demos/course/day5/torus-analytical/**/*.{jpg,jpeg,png,gif,webp,bmp}', {
23
+ const imageFilesRaw = import.meta.glob('/demos/examples/ubo-dynamic/**/*.{jpg,jpeg,png,gif,webp,bmp}', {
13
24
  query: '?url',
14
25
  import: 'default',
15
26
  });
16
- return loadDemo(DEMO_NAME, glslFiles, jsonFiles, imageFiles);
27
+ // Script files (setup.js / script.js hooks for JS-driven computation)
28
+ const scriptFilesRaw = import.meta.glob('/demos/examples/ubo-dynamic/**/script.js');
29
+ // Transform keys to ./ format that loadDemo expects
30
+ const glslFiles = transformKeys(glslFilesRaw);
31
+ const jsonFiles = transformKeys(jsonFilesRaw);
32
+ const imageFiles = transformKeys(imageFilesRaw);
33
+ const scriptFiles = transformKeys(scriptFilesRaw);
34
+ return loadDemo(DEMO_NAME, glslFiles, jsonFiles, imageFiles, scriptFiles);
17
35
  }
@@ -1,22 +1,22 @@
1
1
  /**
2
- * Project Layer - Config Loader
2
+ * Project Layer - Config Loader (Node/CLI)
3
3
  *
4
- * Loads Shadertoy projects from disk into normalized ShadertoyProject representation.
4
+ * Loads shader projects from disk into normalized ShaderProject representation.
5
5
  * Handles both single-pass (no config) and multi-pass (with config) projects.
6
6
  *
7
7
  * Based on docs/project-spec.md
8
8
  */
9
- import { ShadertoyProject } from './types';
9
+ import { ShaderProject } from './types';
10
10
  /**
11
- * Load a Shadertoy project from disk.
11
+ * Load a shader project from disk.
12
12
  *
13
13
  * Automatically detects:
14
14
  * - Single-pass mode (no config, just image.glsl)
15
15
  * - Multi-pass mode (config.json present)
16
16
  *
17
17
  * @param root - Absolute path to project directory
18
- * @returns Fully normalized ShadertoyProject
18
+ * @returns Fully normalized ShaderProject
19
19
  * @throws Error with descriptive message if project is invalid
20
20
  */
21
- export declare function loadProject(root: string): Promise<ShadertoyProject>;
21
+ export declare function loadProject(root: string): Promise<ShaderProject>;
22
22
  //# sourceMappingURL=loadProject.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../../src/project/loadProject.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAQL,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAmEjB;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAoBzE"}
1
+ {"version":3,"file":"loadProject.d.ts","sourceRoot":"","sources":["../../src/project/loadProject.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EASL,aAAa,EAKd,MAAM,SAAS,CAAC;AAuEjB;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAuBtE"}