@fjandin/react-shader 0.0.3 → 0.0.5

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/README.md CHANGED
@@ -53,6 +53,8 @@ function App() {
53
53
  | `fullscreen` | `boolean` | No | `false` | Render as fixed fullscreen overlay |
54
54
  | `timeScale` | `number` | No | `1` | Scale factor for elapsed time |
55
55
  | `onFrame` | `(info: FrameInfo) => void` | No | - | Callback invoked on each frame |
56
+ | `onClick` | `(info: FrameInfo) => void` | No | - | Callback invoked on canvas click |
57
+ | `onMouseMove` | `(info: FrameInfo) => void` | No | - | Callback invoked on mouse move |
56
58
 
57
59
  ## Built-in Uniforms
58
60
 
@@ -62,6 +64,7 @@ These uniforms are automatically provided to your shader every frame:
62
64
  |---------|-----------|-------------|
63
65
  | `iTime` | `float` | Elapsed time in seconds (scaled by `timeScale` prop) |
64
66
  | `iMouse` | `vec2` | Mouse position in pixels (Y=0 at bottom) |
67
+ | `iMouseNormalized` | `vec2` | Mouse position normalized with aspect correction (shorter axis -0.5 to 0.5, center is 0,0) |
65
68
  | `iMouseLeftDown` | `float` | `1.0` when left mouse button is pressed, `0.0` otherwise |
66
69
  | `iResolution` | `vec2` | Canvas resolution in pixels (includes high-DPI scaling) |
67
70
 
@@ -73,10 +76,14 @@ Pass custom uniform values via the `uniforms` prop:
73
76
  <ReactShader
74
77
  fragment={fragment}
75
78
  uniforms={{
76
- scale: 2.0, // float
77
- offset: [0.5, 0.5], // vec2
78
- color: [1.0, 0.5, 0.2], // vec3
79
- transform: [1, 0, 0, 1], // vec4
79
+ scale: 2.0, // float
80
+ offset: [0.5, 0.5], // vec2
81
+ color: [1.0, 0.5, 0.2], // vec3
82
+ transform: [1, 0, 0, 1], // vec4
83
+ weights: [0.1, 0.2, 0.3, 0.25, 0.15], // float array
84
+ points: [[0, 0], [1, 0], [0.5, 1]], // vec2 array
85
+ colors: [[1, 0, 0], [0, 1, 0]], // vec3 array
86
+ ripples: [[0, 0, 0.5, 0.1]], // vec4 array
80
87
  }}
81
88
  />
82
89
  ```
@@ -86,6 +93,62 @@ Supported types:
86
93
  - `[number, number]` → `vec2`
87
94
  - `[number, number, number]` → `vec3`
88
95
  - `[number, number, number, number]` → `vec4`
96
+ - `number[]` (length > 4) → `float[N]`
97
+ - `[number, number][]` → `vec2[N]`
98
+ - `[number, number, number][]` → `vec3[N]`
99
+ - `[number, number, number, number][]` → `vec4[N]`
100
+
101
+ ### Array Count Uniforms
102
+
103
+ For array uniforms, an additional `_count` uniform is automatically created and set:
104
+
105
+ ```tsx
106
+ uniforms={{ points: [[0, 0], [1, 0], [0.5, 1]] }}
107
+ ```
108
+
109
+ This generates both `uniform vec2 points[3]` and `uniform int points_count` (set to `3`), allowing you to loop over arrays in your shader:
110
+
111
+ ```glsl
112
+ for (int i = 0; i < points_count; i++) {
113
+ // Use points[i]...
114
+ }
115
+ ```
116
+
117
+ ## Automatic Uniform Injection
118
+
119
+ Instead of manually declaring uniforms in your shader, you can use the `// @UNIFORM_VALUES` marker to automatically inject all uniform declarations:
120
+
121
+ ```tsx
122
+ const fragment = `#version 300 es
123
+ precision highp float;
124
+
125
+ // @UNIFORM_VALUES
126
+
127
+ out vec4 fragColor;
128
+
129
+ void main() {
130
+ vec2 uv = gl_FragCoord.xy / iResolution;
131
+ vec3 col = baseColor * (sin(iTime) * 0.5 + 0.5);
132
+ fragColor = vec4(col, 1.0);
133
+ }
134
+ `
135
+
136
+ <ReactShader
137
+ fragment={fragment}
138
+ uniforms={{ baseColor: [1.0, 0.5, 0.2] }}
139
+ />
140
+ ```
141
+
142
+ The marker gets replaced with declarations for both built-in uniforms (`iTime`, `iMouse`, `iMouseNormalized`, `iMouseLeftDown`, `iResolution`) and your custom uniforms:
143
+
144
+ ```glsl
145
+ uniform float iTime;
146
+ uniform vec2 iMouse;
147
+ uniform vec2 iMouseNormalized;
148
+ uniform float iMouseLeftDown;
149
+ uniform vec2 iResolution;
150
+ uniform vec3 baseColor;
151
+ ```
89
152
 
90
153
  ## Frame Callback
91
154
 
@@ -112,6 +175,7 @@ The `FrameInfo` object contains:
112
175
  - `time` - Total elapsed time in seconds
113
176
  - `resolution` - Canvas resolution as `[width, height]`
114
177
  - `mouse` - Mouse position as `[x, y]`
178
+ - `mouseLeftDown` - Whether left mouse button is pressed
115
179
 
116
180
  ## TypeScript
117
181
 
@@ -122,6 +186,10 @@ import type {
122
186
  Vec2,
123
187
  Vec3,
124
188
  Vec4,
189
+ FloatArray,
190
+ Vec2Array,
191
+ Vec3Array,
192
+ Vec4Array,
125
193
  UniformValue,
126
194
  DefaultUniforms,
127
195
  FrameInfo,
@@ -129,6 +197,21 @@ import type {
129
197
  } from "@fjandin/react-shader"
130
198
  ```
131
199
 
200
+ A utility function for generating uniform declarations is also exported:
201
+
202
+ ```tsx
203
+ import { generateUniformDeclarations } from "@fjandin/react-shader"
204
+
205
+ const declarations = generateUniformDeclarations({
206
+ scale: 1.0,
207
+ points: [[0, 0], [1, 1]],
208
+ })
209
+ // Returns:
210
+ // uniform float scale;
211
+ // uniform vec2 points[2];
212
+ // uniform int points_count;
213
+ ```
214
+
132
215
  ## Features
133
216
 
134
217
  - WebGL2 with WebGL1 fallback
@@ -1,3 +1,3 @@
1
1
  import type { ReactShaderProps } from "./types";
2
- export declare function ReactShader({ className, fragment, vertex, uniforms, debug, fullscreen, timeScale, onFrame, }: ReactShaderProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function ReactShader({ className, fragment, vertex, uniforms, fullscreen, timeScale, onFrame, onClick, onMouseMove, }: ReactShaderProps): import("react/jsx-runtime").JSX.Element;
3
3
  //# sourceMappingURL=ReactShader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,SAAS,CAAA;AAerD,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,MAAuB,EACvB,QAAQ,EACR,KAAa,EACb,UAAkB,EAClB,SAAa,EACb,OAAO,GACR,EAAE,gBAAgB,2CAiHlB"}
1
+ {"version":3,"file":"ReactShader.d.ts","sourceRoot":"","sources":["../src/ReactShader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAU/C,wBAAgB,WAAW,CAAC,EAC1B,SAAS,EACT,QAAQ,EACR,MAAuB,EACvB,QAAQ,EACR,UAAkB,EAClB,SAAa,EACb,OAAO,EACP,OAAO,EACP,WAAW,GACZ,EAAE,gBAAgB,2CA0ElB"}
@@ -1 +1 @@
1
- {"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/example/frontend.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,aAAa,CAAA;AAsBpB,wBAAgB,GAAG,4CAyBlB"}
1
+ {"version":3,"file":"frontend.d.ts","sourceRoot":"","sources":["../../src/example/frontend.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,aAAa,CAAA;AAyBpB,wBAAgB,GAAG,4CAwFlB"}
@@ -0,0 +1,2 @@
1
+ export declare const cartesianToPolar = "\n vec2 cartesianToPolar(vec2 cartesian, float seamAngleDegrees) {\n float radius = length(cartesian);\n // Rotate input to move where the seam appears\n float rot = radians(seamAngleDegrees);\n vec2 rotated = vec2(\n cartesian.x * cos(rot) + cartesian.y * sin(rot),\n -cartesian.x * sin(rot) + cartesian.y * cos(rot)\n );\n float angle = atan(rotated.y, rotated.x);\n // Map angle from [-PI, PI] to [-0.5, 0.5] range\n return vec2(angle / 6.28318, radius);\n }\n";
2
+ //# sourceMappingURL=cartesian-to-polar.glsl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cartesian-to-polar.glsl.d.ts","sourceRoot":"","sources":["../../../src/example/glsl/cartesian-to-polar.glsl.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,wfAa5B,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare function log(...args: any[]): void;
2
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/example/lib/logger.ts"],"names":[],"mappings":"AACA,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,QAejC"}
@@ -1 +1 @@
1
- {"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../../src/example/shader.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,QAsFnB,CAAA"}
1
+ {"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../../src/example/shader.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,QA4FnB,CAAA"}
@@ -4,6 +4,8 @@ export interface FrameInfo {
4
4
  time: number;
5
5
  resolution: [number, number];
6
6
  mouse: [number, number];
7
+ mouseNormalized: [number, number];
8
+ mouseLeftDown: boolean;
7
9
  }
8
10
  interface UseWebGLOptions {
9
11
  fragment: string;
@@ -11,6 +13,8 @@ interface UseWebGLOptions {
11
13
  uniforms?: Record<string, UniformValue>;
12
14
  onError?: (error: Error) => void;
13
15
  onFrame?: (info: FrameInfo) => void;
16
+ onClick?: (info: FrameInfo) => void;
17
+ onMouseMove?: (info: FrameInfo) => void;
14
18
  running?: boolean;
15
19
  timeScale?: number;
16
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useWebGL.d.ts","sourceRoot":"","sources":["../../src/hooks/useWebGL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAI5C,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAoDD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe;;;EAiNhD"}
1
+ {"version":3,"file":"useWebGL.d.ts","sourceRoot":"","sources":["../../src/hooks/useWebGL.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAI5C,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvB,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACjC,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAqED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe;;;EA+PhD"}
package/dist/index.cjs CHANGED
@@ -29,6 +29,7 @@ var __export = (target, all) => {
29
29
  // src/index.ts
30
30
  var exports_src = {};
31
31
  __export(exports_src, {
32
+ generateUniformDeclarations: () => generateUniformDeclarations,
32
33
  ReactShader: () => ReactShader
33
34
  });
34
35
  module.exports = __toCommonJS(exports_src);
@@ -84,14 +85,30 @@ function createShaderProgram(gl, vertexSource, fragmentSource) {
84
85
  }
85
86
 
86
87
  // src/utils/uniforms.ts
88
+ var MAX_ARRAY_LENGTH = 100;
87
89
  function isVec2(value) {
88
- return Array.isArray(value) && value.length === 2;
90
+ return Array.isArray(value) && value.length === 2 && typeof value[0] === "number";
89
91
  }
90
92
  function isVec3(value) {
91
- return Array.isArray(value) && value.length === 3;
93
+ return Array.isArray(value) && value.length === 3 && typeof value[0] === "number";
92
94
  }
93
95
  function isVec4(value) {
94
- return Array.isArray(value) && value.length === 4;
96
+ return Array.isArray(value) && value.length === 4 && typeof value[0] === "number";
97
+ }
98
+ function isFloatArray(value) {
99
+ return Array.isArray(value) && value.length > 4 && typeof value[0] === "number";
100
+ }
101
+ function isVec2Array(value) {
102
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 2;
103
+ }
104
+ function isVec3Array(value) {
105
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 3;
106
+ }
107
+ function isVec4Array(value) {
108
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 4;
109
+ }
110
+ function isArrayUniform(value) {
111
+ return isFloatArray(value) || isVec2Array(value) || isVec3Array(value) || isVec4Array(value);
95
112
  }
96
113
  function setUniform(gl, location, value) {
97
114
  if (location === null) {
@@ -99,6 +116,14 @@ function setUniform(gl, location, value) {
99
116
  }
100
117
  if (typeof value === "number") {
101
118
  gl.uniform1f(location, value);
119
+ } else if (isVec4Array(value)) {
120
+ gl.uniform4fv(location, value.flat());
121
+ } else if (isVec3Array(value)) {
122
+ gl.uniform3fv(location, value.flat());
123
+ } else if (isVec2Array(value)) {
124
+ gl.uniform2fv(location, value.flat());
125
+ } else if (isFloatArray(value)) {
126
+ gl.uniform1fv(location, value);
102
127
  } else if (isVec4(value)) {
103
128
  gl.uniform4f(location, value[0], value[1], value[2], value[3]);
104
129
  } else if (isVec3(value)) {
@@ -118,19 +143,90 @@ function setUniforms(gl, program, uniforms, locationCache) {
118
143
  locationCache.set(name, location);
119
144
  }
120
145
  setUniform(gl, location, value);
146
+ if (isArrayUniform(value)) {
147
+ const countName = `${name}_count`;
148
+ let countLocation = locationCache.get(countName);
149
+ if (countLocation === undefined) {
150
+ countLocation = getUniformLocation(gl, program, countName);
151
+ locationCache.set(countName, countLocation);
152
+ }
153
+ if (countLocation !== null) {
154
+ gl.uniform1i(countLocation, value.length);
155
+ }
156
+ }
121
157
  }
122
158
  }
123
159
  function createUniformLocationCache() {
124
160
  return new Map;
125
161
  }
162
+ function getUniformType(value) {
163
+ if (typeof value === "number") {
164
+ return "float";
165
+ }
166
+ if (isVec4Array(value)) {
167
+ return `vec4[${MAX_ARRAY_LENGTH}]`;
168
+ }
169
+ if (isVec3Array(value)) {
170
+ return `vec3[${MAX_ARRAY_LENGTH}]`;
171
+ }
172
+ if (isVec2Array(value)) {
173
+ return `vec2[${MAX_ARRAY_LENGTH}]`;
174
+ }
175
+ if (isFloatArray(value)) {
176
+ return `float[${MAX_ARRAY_LENGTH}]`;
177
+ }
178
+ if (isVec4(value)) {
179
+ return "vec4";
180
+ }
181
+ if (isVec3(value)) {
182
+ return "vec3";
183
+ }
184
+ if (isVec2(value)) {
185
+ return "vec2";
186
+ }
187
+ return "float";
188
+ }
189
+ function generateUniformDeclarations(uniforms) {
190
+ const lines = [];
191
+ for (const [name, value] of Object.entries(uniforms)) {
192
+ const type = getUniformType(value);
193
+ if (type.includes("[")) {
194
+ const [baseType, arrayPart] = type.split("[");
195
+ lines.push(`uniform ${baseType} ${name}[${arrayPart};`);
196
+ lines.push(`uniform int ${name}_count;`);
197
+ } else {
198
+ lines.push(`uniform ${type} ${name};`);
199
+ }
200
+ }
201
+ return lines.join(`
202
+ `);
203
+ }
204
+ var UNIFORM_MARKER = "// @UNIFORM_VALUES";
205
+ function injectUniformDeclarations(shaderSource, customUniforms, defaultUniforms) {
206
+ if (!shaderSource.includes(UNIFORM_MARKER)) {
207
+ return shaderSource;
208
+ }
209
+ const allUniforms = { ...defaultUniforms, ...customUniforms };
210
+ const declarations = generateUniformDeclarations(allUniforms);
211
+ return shaderSource.replace(UNIFORM_MARKER, declarations);
212
+ }
126
213
 
127
214
  // src/hooks/useWebGL.ts
128
- function initializeWebGL(canvas, vertexSource, fragmentSource) {
215
+ var DEFAULT_UNIFORM_TYPES = {
216
+ iTime: 0,
217
+ iMouse: [0, 0],
218
+ iMouseNormalized: [0, 0],
219
+ iMouseLeftDown: 0,
220
+ iResolution: [0, 0]
221
+ };
222
+ function initializeWebGL(canvas, vertexSource, fragmentSource, customUniforms) {
129
223
  const gl = canvas.getContext("webgl2") || canvas.getContext("webgl");
130
224
  if (!gl) {
131
225
  throw new Error("WebGL not supported");
132
226
  }
133
- const program = createShaderProgram(gl, vertexSource, fragmentSource);
227
+ const processedVertex = injectUniformDeclarations(vertexSource, customUniforms, DEFAULT_UNIFORM_TYPES);
228
+ const processedFragment = injectUniformDeclarations(fragmentSource, customUniforms, DEFAULT_UNIFORM_TYPES);
229
+ const program = createShaderProgram(gl, processedVertex, processedFragment);
134
230
  const positionBuffer = gl.createBuffer();
135
231
  if (!positionBuffer) {
136
232
  throw new Error("Failed to create position buffer");
@@ -161,18 +257,23 @@ function useWebGL(options) {
161
257
  const elapsedTimeRef = import_react.useRef(0);
162
258
  const lastFrameTimeRef = import_react.useRef(0);
163
259
  const mouseRef = import_react.useRef([0, 0]);
260
+ const mouseNormalizedRef = import_react.useRef([0, 0]);
164
261
  const mouseLeftDownRef = import_react.useRef(false);
165
262
  const canvasRectRef = import_react.useRef(null);
166
263
  const contextLostRef = import_react.useRef(false);
167
264
  const uniformsRef = import_react.useRef(options.uniforms);
168
265
  const onErrorRef = import_react.useRef(options.onError);
169
266
  const onFrameRef = import_react.useRef(options.onFrame);
267
+ const onClickRef = import_react.useRef(options.onClick);
268
+ const onMouseMoveRef = import_react.useRef(options.onMouseMove);
170
269
  const timeScaleRef = import_react.useRef(options.timeScale ?? 1);
171
270
  const vertexRef = import_react.useRef(options.vertex);
172
271
  const fragmentRef = import_react.useRef(options.fragment);
173
272
  uniformsRef.current = options.uniforms;
174
273
  onErrorRef.current = options.onError;
175
274
  onFrameRef.current = options.onFrame;
275
+ onClickRef.current = options.onClick;
276
+ onMouseMoveRef.current = options.onMouseMove;
176
277
  timeScaleRef.current = options.timeScale ?? 1;
177
278
  vertexRef.current = options.vertex;
178
279
  fragmentRef.current = options.fragment;
@@ -208,9 +309,15 @@ function useWebGL(options) {
208
309
  gl.enableVertexAttribArray(positionAttributeLocation);
209
310
  gl.bindBuffer(gl.ARRAY_BUFFER, state.positionBuffer);
210
311
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
312
+ const minDimension = Math.min(canvas.width, canvas.height) || 1;
313
+ mouseNormalizedRef.current = [
314
+ (mouseRef.current[0] - canvas.width / 2) / minDimension,
315
+ (mouseRef.current[1] - canvas.height / 2) / minDimension
316
+ ];
211
317
  const defaultUniforms = {
212
318
  iTime: elapsedTime,
213
319
  iMouse: mouseRef.current,
320
+ iMouseNormalized: mouseNormalizedRef.current,
214
321
  iMouseLeftDown: mouseLeftDownRef.current ? 1 : 0,
215
322
  iResolution: [canvas.width, canvas.height]
216
323
  };
@@ -224,7 +331,9 @@ function useWebGL(options) {
224
331
  deltaTime,
225
332
  time: elapsedTime,
226
333
  resolution: [canvas.width, canvas.height],
227
- mouse: mouseRef.current
334
+ mouse: mouseRef.current,
335
+ mouseNormalized: mouseNormalizedRef.current,
336
+ mouseLeftDown: mouseLeftDownRef.current
228
337
  });
229
338
  }
230
339
  animationFrameRef.current = requestAnimationFrame(render);
@@ -235,7 +344,7 @@ function useWebGL(options) {
235
344
  return;
236
345
  const initialize = () => {
237
346
  try {
238
- stateRef.current = initializeWebGL(canvas, vertexRef.current, fragmentRef.current);
347
+ stateRef.current = initializeWebGL(canvas, vertexRef.current, fragmentRef.current, uniformsRef.current);
239
348
  elapsedTimeRef.current = 0;
240
349
  lastFrameTimeRef.current = 0;
241
350
  contextLostRef.current = false;
@@ -290,6 +399,19 @@ function useWebGL(options) {
290
399
  const x = (event.clientX - rect.left) * dpr;
291
400
  const y = (rect.height - (event.clientY - rect.top)) * dpr;
292
401
  mouseRef.current = [x, y];
402
+ const minDimension = Math.min(canvas.width, canvas.height) || 1;
403
+ mouseNormalizedRef.current = [
404
+ (mouseRef.current[0] - canvas.width / 2) / minDimension,
405
+ (mouseRef.current[1] - canvas.height / 2) / minDimension
406
+ ];
407
+ onMouseMoveRef.current?.({
408
+ deltaTime: 0,
409
+ time: elapsedTimeRef.current,
410
+ resolution: [canvas.width, canvas.height],
411
+ mouse: mouseRef.current,
412
+ mouseNormalized: mouseNormalizedRef.current,
413
+ mouseLeftDown: mouseLeftDownRef.current
414
+ });
293
415
  };
294
416
  const handleMouseDown = (event) => {
295
417
  if (event.button === 0) {
@@ -301,15 +423,29 @@ function useWebGL(options) {
301
423
  mouseLeftDownRef.current = false;
302
424
  }
303
425
  };
426
+ const handleClick = () => {
427
+ if (!onClickRef.current)
428
+ return;
429
+ onClickRef.current({
430
+ deltaTime: 0,
431
+ time: elapsedTimeRef.current,
432
+ resolution: [canvas.width, canvas.height],
433
+ mouse: mouseRef.current,
434
+ mouseNormalized: mouseNormalizedRef.current,
435
+ mouseLeftDown: mouseLeftDownRef.current
436
+ });
437
+ };
304
438
  window.addEventListener("mousemove", handleMouseMove);
305
439
  window.addEventListener("mousedown", handleMouseDown);
306
440
  window.addEventListener("mouseup", handleMouseUp);
441
+ canvas.addEventListener("click", handleClick);
307
442
  return () => {
308
443
  resizeObserver.disconnect();
309
444
  window.removeEventListener("scroll", updateRect);
310
445
  window.removeEventListener("mousemove", handleMouseMove);
311
446
  window.removeEventListener("mousedown", handleMouseDown);
312
447
  window.removeEventListener("mouseup", handleMouseUp);
448
+ canvas.removeEventListener("click", handleClick);
313
449
  };
314
450
  }, []);
315
451
  return { canvasRef, mouseRef };
@@ -329,17 +465,13 @@ function ReactShader({
329
465
  fragment,
330
466
  vertex = DEFAULT_VERTEX,
331
467
  uniforms,
332
- debug = false,
333
468
  fullscreen = false,
334
469
  timeScale = 1,
335
- onFrame
470
+ onFrame,
471
+ onClick,
472
+ onMouseMove
336
473
  }) {
337
474
  const [error, setError] = import_react2.useState(null);
338
- const [debugInfo, setDebugInfo] = import_react2.useState({
339
- iResolution: [0, 0],
340
- iMouse: [0, 0]
341
- });
342
- const lastDebugUpdateRef = import_react2.useRef(0);
343
475
  const handleError = import_react2.useCallback((err) => {
344
476
  setError(err.message);
345
477
  console.error("ReactShader error:", err);
@@ -348,17 +480,7 @@ function ReactShader({
348
480
  if (onFrame) {
349
481
  onFrame(info);
350
482
  }
351
- if (!debug)
352
- return;
353
- const now = performance.now();
354
- if (now - lastDebugUpdateRef.current < 100)
355
- return;
356
- lastDebugUpdateRef.current = now;
357
- setDebugInfo({
358
- iResolution: info.resolution,
359
- iMouse: info.mouse
360
- });
361
- }, [debug, onFrame]);
483
+ }, [onFrame]);
362
484
  import_react2.useEffect(() => {
363
485
  setError(null);
364
486
  }, [fragment, vertex]);
@@ -368,6 +490,8 @@ function ReactShader({
368
490
  uniforms,
369
491
  onError: handleError,
370
492
  onFrame: handleFrame,
493
+ onClick,
494
+ onMouseMove,
371
495
  timeScale
372
496
  });
373
497
  const containerStyle = fullscreen ? {
@@ -396,7 +520,9 @@ function ReactShader({
396
520
  fontSize: "12px",
397
521
  padding: "16px",
398
522
  overflow: "auto",
399
- boxSizing: "border-box"
523
+ boxSizing: "border-box",
524
+ width: "100%",
525
+ height: "100%"
400
526
  },
401
527
  children: /* @__PURE__ */ jsx_dev_runtime.jsxDEV("pre", {
402
528
  style: { margin: 0, whiteSpace: "pre-wrap" },
@@ -404,48 +530,9 @@ function ReactShader({
404
530
  }, undefined, false, undefined, this)
405
531
  }, undefined, false, undefined, this);
406
532
  }
407
- return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
408
- style: containerStyle,
409
- children: [
410
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV("canvas", {
411
- ref: canvasRef,
412
- className,
413
- style: { display: "block", width: "100%", height: "100%" }
414
- }, undefined, false, undefined, this),
415
- debug && /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
416
- style: {
417
- position: "absolute",
418
- top: 8,
419
- left: 8,
420
- backgroundColor: "rgba(0, 0, 0, 0.7)",
421
- color: "#fff",
422
- fontFamily: "monospace",
423
- fontSize: "12px",
424
- padding: "8px",
425
- borderRadius: "4px",
426
- pointerEvents: "none"
427
- },
428
- children: [
429
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
430
- children: [
431
- "iResolution: [",
432
- debugInfo.iResolution[0],
433
- ", ",
434
- debugInfo.iResolution[1],
435
- "]"
436
- ]
437
- }, undefined, true, undefined, this),
438
- /* @__PURE__ */ jsx_dev_runtime.jsxDEV("div", {
439
- children: [
440
- "iMouse: [",
441
- Math.round(debugInfo.iMouse[0]),
442
- ", ",
443
- Math.round(debugInfo.iMouse[1]),
444
- "]"
445
- ]
446
- }, undefined, true, undefined, this)
447
- ]
448
- }, undefined, true, undefined, this)
449
- ]
450
- }, undefined, true, undefined, this);
533
+ return /* @__PURE__ */ jsx_dev_runtime.jsxDEV("canvas", {
534
+ ref: canvasRef,
535
+ className,
536
+ style: { display: "block", width: "100%", height: "100%" }
537
+ }, undefined, false, undefined, this);
451
538
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { ReactShader } from "./ReactShader";
2
- export type { DefaultUniforms, ReactShaderProps, UniformValue, Vec2, Vec3, Vec4, } from "./types";
2
+ export type { DefaultUniforms, FloatArray, ReactShaderProps, UniformValue, Vec2, Vec2Array, Vec3, Vec3Array, Vec4, Vec4Array, } from "./types";
3
+ export { generateUniformDeclarations } from "./utils/uniforms";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,IAAI,EACJ,IAAI,EACJ,IAAI,GACL,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,YAAY,EACV,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/ReactShader.tsx
2
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState } from "react";
2
+ import { useCallback as useCallback2, useEffect as useEffect2, useState } from "react";
3
3
 
4
4
  // src/hooks/useWebGL.ts
5
5
  import { useCallback, useEffect, useRef } from "react";
@@ -49,14 +49,30 @@ function createShaderProgram(gl, vertexSource, fragmentSource) {
49
49
  }
50
50
 
51
51
  // src/utils/uniforms.ts
52
+ var MAX_ARRAY_LENGTH = 100;
52
53
  function isVec2(value) {
53
- return Array.isArray(value) && value.length === 2;
54
+ return Array.isArray(value) && value.length === 2 && typeof value[0] === "number";
54
55
  }
55
56
  function isVec3(value) {
56
- return Array.isArray(value) && value.length === 3;
57
+ return Array.isArray(value) && value.length === 3 && typeof value[0] === "number";
57
58
  }
58
59
  function isVec4(value) {
59
- return Array.isArray(value) && value.length === 4;
60
+ return Array.isArray(value) && value.length === 4 && typeof value[0] === "number";
61
+ }
62
+ function isFloatArray(value) {
63
+ return Array.isArray(value) && value.length > 4 && typeof value[0] === "number";
64
+ }
65
+ function isVec2Array(value) {
66
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 2;
67
+ }
68
+ function isVec3Array(value) {
69
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 3;
70
+ }
71
+ function isVec4Array(value) {
72
+ return Array.isArray(value) && value.length > 0 && Array.isArray(value[0]) && value[0].length === 4;
73
+ }
74
+ function isArrayUniform(value) {
75
+ return isFloatArray(value) || isVec2Array(value) || isVec3Array(value) || isVec4Array(value);
60
76
  }
61
77
  function setUniform(gl, location, value) {
62
78
  if (location === null) {
@@ -64,6 +80,14 @@ function setUniform(gl, location, value) {
64
80
  }
65
81
  if (typeof value === "number") {
66
82
  gl.uniform1f(location, value);
83
+ } else if (isVec4Array(value)) {
84
+ gl.uniform4fv(location, value.flat());
85
+ } else if (isVec3Array(value)) {
86
+ gl.uniform3fv(location, value.flat());
87
+ } else if (isVec2Array(value)) {
88
+ gl.uniform2fv(location, value.flat());
89
+ } else if (isFloatArray(value)) {
90
+ gl.uniform1fv(location, value);
67
91
  } else if (isVec4(value)) {
68
92
  gl.uniform4f(location, value[0], value[1], value[2], value[3]);
69
93
  } else if (isVec3(value)) {
@@ -83,19 +107,90 @@ function setUniforms(gl, program, uniforms, locationCache) {
83
107
  locationCache.set(name, location);
84
108
  }
85
109
  setUniform(gl, location, value);
110
+ if (isArrayUniform(value)) {
111
+ const countName = `${name}_count`;
112
+ let countLocation = locationCache.get(countName);
113
+ if (countLocation === undefined) {
114
+ countLocation = getUniformLocation(gl, program, countName);
115
+ locationCache.set(countName, countLocation);
116
+ }
117
+ if (countLocation !== null) {
118
+ gl.uniform1i(countLocation, value.length);
119
+ }
120
+ }
86
121
  }
87
122
  }
88
123
  function createUniformLocationCache() {
89
124
  return new Map;
90
125
  }
126
+ function getUniformType(value) {
127
+ if (typeof value === "number") {
128
+ return "float";
129
+ }
130
+ if (isVec4Array(value)) {
131
+ return `vec4[${MAX_ARRAY_LENGTH}]`;
132
+ }
133
+ if (isVec3Array(value)) {
134
+ return `vec3[${MAX_ARRAY_LENGTH}]`;
135
+ }
136
+ if (isVec2Array(value)) {
137
+ return `vec2[${MAX_ARRAY_LENGTH}]`;
138
+ }
139
+ if (isFloatArray(value)) {
140
+ return `float[${MAX_ARRAY_LENGTH}]`;
141
+ }
142
+ if (isVec4(value)) {
143
+ return "vec4";
144
+ }
145
+ if (isVec3(value)) {
146
+ return "vec3";
147
+ }
148
+ if (isVec2(value)) {
149
+ return "vec2";
150
+ }
151
+ return "float";
152
+ }
153
+ function generateUniformDeclarations(uniforms) {
154
+ const lines = [];
155
+ for (const [name, value] of Object.entries(uniforms)) {
156
+ const type = getUniformType(value);
157
+ if (type.includes("[")) {
158
+ const [baseType, arrayPart] = type.split("[");
159
+ lines.push(`uniform ${baseType} ${name}[${arrayPart};`);
160
+ lines.push(`uniform int ${name}_count;`);
161
+ } else {
162
+ lines.push(`uniform ${type} ${name};`);
163
+ }
164
+ }
165
+ return lines.join(`
166
+ `);
167
+ }
168
+ var UNIFORM_MARKER = "// @UNIFORM_VALUES";
169
+ function injectUniformDeclarations(shaderSource, customUniforms, defaultUniforms) {
170
+ if (!shaderSource.includes(UNIFORM_MARKER)) {
171
+ return shaderSource;
172
+ }
173
+ const allUniforms = { ...defaultUniforms, ...customUniforms };
174
+ const declarations = generateUniformDeclarations(allUniforms);
175
+ return shaderSource.replace(UNIFORM_MARKER, declarations);
176
+ }
91
177
 
92
178
  // src/hooks/useWebGL.ts
93
- function initializeWebGL(canvas, vertexSource, fragmentSource) {
179
+ var DEFAULT_UNIFORM_TYPES = {
180
+ iTime: 0,
181
+ iMouse: [0, 0],
182
+ iMouseNormalized: [0, 0],
183
+ iMouseLeftDown: 0,
184
+ iResolution: [0, 0]
185
+ };
186
+ function initializeWebGL(canvas, vertexSource, fragmentSource, customUniforms) {
94
187
  const gl = canvas.getContext("webgl2") || canvas.getContext("webgl");
95
188
  if (!gl) {
96
189
  throw new Error("WebGL not supported");
97
190
  }
98
- const program = createShaderProgram(gl, vertexSource, fragmentSource);
191
+ const processedVertex = injectUniformDeclarations(vertexSource, customUniforms, DEFAULT_UNIFORM_TYPES);
192
+ const processedFragment = injectUniformDeclarations(fragmentSource, customUniforms, DEFAULT_UNIFORM_TYPES);
193
+ const program = createShaderProgram(gl, processedVertex, processedFragment);
99
194
  const positionBuffer = gl.createBuffer();
100
195
  if (!positionBuffer) {
101
196
  throw new Error("Failed to create position buffer");
@@ -126,18 +221,23 @@ function useWebGL(options) {
126
221
  const elapsedTimeRef = useRef(0);
127
222
  const lastFrameTimeRef = useRef(0);
128
223
  const mouseRef = useRef([0, 0]);
224
+ const mouseNormalizedRef = useRef([0, 0]);
129
225
  const mouseLeftDownRef = useRef(false);
130
226
  const canvasRectRef = useRef(null);
131
227
  const contextLostRef = useRef(false);
132
228
  const uniformsRef = useRef(options.uniforms);
133
229
  const onErrorRef = useRef(options.onError);
134
230
  const onFrameRef = useRef(options.onFrame);
231
+ const onClickRef = useRef(options.onClick);
232
+ const onMouseMoveRef = useRef(options.onMouseMove);
135
233
  const timeScaleRef = useRef(options.timeScale ?? 1);
136
234
  const vertexRef = useRef(options.vertex);
137
235
  const fragmentRef = useRef(options.fragment);
138
236
  uniformsRef.current = options.uniforms;
139
237
  onErrorRef.current = options.onError;
140
238
  onFrameRef.current = options.onFrame;
239
+ onClickRef.current = options.onClick;
240
+ onMouseMoveRef.current = options.onMouseMove;
141
241
  timeScaleRef.current = options.timeScale ?? 1;
142
242
  vertexRef.current = options.vertex;
143
243
  fragmentRef.current = options.fragment;
@@ -173,9 +273,15 @@ function useWebGL(options) {
173
273
  gl.enableVertexAttribArray(positionAttributeLocation);
174
274
  gl.bindBuffer(gl.ARRAY_BUFFER, state.positionBuffer);
175
275
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
276
+ const minDimension = Math.min(canvas.width, canvas.height) || 1;
277
+ mouseNormalizedRef.current = [
278
+ (mouseRef.current[0] - canvas.width / 2) / minDimension,
279
+ (mouseRef.current[1] - canvas.height / 2) / minDimension
280
+ ];
176
281
  const defaultUniforms = {
177
282
  iTime: elapsedTime,
178
283
  iMouse: mouseRef.current,
284
+ iMouseNormalized: mouseNormalizedRef.current,
179
285
  iMouseLeftDown: mouseLeftDownRef.current ? 1 : 0,
180
286
  iResolution: [canvas.width, canvas.height]
181
287
  };
@@ -189,7 +295,9 @@ function useWebGL(options) {
189
295
  deltaTime,
190
296
  time: elapsedTime,
191
297
  resolution: [canvas.width, canvas.height],
192
- mouse: mouseRef.current
298
+ mouse: mouseRef.current,
299
+ mouseNormalized: mouseNormalizedRef.current,
300
+ mouseLeftDown: mouseLeftDownRef.current
193
301
  });
194
302
  }
195
303
  animationFrameRef.current = requestAnimationFrame(render);
@@ -200,7 +308,7 @@ function useWebGL(options) {
200
308
  return;
201
309
  const initialize = () => {
202
310
  try {
203
- stateRef.current = initializeWebGL(canvas, vertexRef.current, fragmentRef.current);
311
+ stateRef.current = initializeWebGL(canvas, vertexRef.current, fragmentRef.current, uniformsRef.current);
204
312
  elapsedTimeRef.current = 0;
205
313
  lastFrameTimeRef.current = 0;
206
314
  contextLostRef.current = false;
@@ -255,6 +363,19 @@ function useWebGL(options) {
255
363
  const x = (event.clientX - rect.left) * dpr;
256
364
  const y = (rect.height - (event.clientY - rect.top)) * dpr;
257
365
  mouseRef.current = [x, y];
366
+ const minDimension = Math.min(canvas.width, canvas.height) || 1;
367
+ mouseNormalizedRef.current = [
368
+ (mouseRef.current[0] - canvas.width / 2) / minDimension,
369
+ (mouseRef.current[1] - canvas.height / 2) / minDimension
370
+ ];
371
+ onMouseMoveRef.current?.({
372
+ deltaTime: 0,
373
+ time: elapsedTimeRef.current,
374
+ resolution: [canvas.width, canvas.height],
375
+ mouse: mouseRef.current,
376
+ mouseNormalized: mouseNormalizedRef.current,
377
+ mouseLeftDown: mouseLeftDownRef.current
378
+ });
258
379
  };
259
380
  const handleMouseDown = (event) => {
260
381
  if (event.button === 0) {
@@ -266,15 +387,29 @@ function useWebGL(options) {
266
387
  mouseLeftDownRef.current = false;
267
388
  }
268
389
  };
390
+ const handleClick = () => {
391
+ if (!onClickRef.current)
392
+ return;
393
+ onClickRef.current({
394
+ deltaTime: 0,
395
+ time: elapsedTimeRef.current,
396
+ resolution: [canvas.width, canvas.height],
397
+ mouse: mouseRef.current,
398
+ mouseNormalized: mouseNormalizedRef.current,
399
+ mouseLeftDown: mouseLeftDownRef.current
400
+ });
401
+ };
269
402
  window.addEventListener("mousemove", handleMouseMove);
270
403
  window.addEventListener("mousedown", handleMouseDown);
271
404
  window.addEventListener("mouseup", handleMouseUp);
405
+ canvas.addEventListener("click", handleClick);
272
406
  return () => {
273
407
  resizeObserver.disconnect();
274
408
  window.removeEventListener("scroll", updateRect);
275
409
  window.removeEventListener("mousemove", handleMouseMove);
276
410
  window.removeEventListener("mousedown", handleMouseDown);
277
411
  window.removeEventListener("mouseup", handleMouseUp);
412
+ canvas.removeEventListener("click", handleClick);
278
413
  };
279
414
  }, []);
280
415
  return { canvasRef, mouseRef };
@@ -294,17 +429,13 @@ function ReactShader({
294
429
  fragment,
295
430
  vertex = DEFAULT_VERTEX,
296
431
  uniforms,
297
- debug = false,
298
432
  fullscreen = false,
299
433
  timeScale = 1,
300
- onFrame
434
+ onFrame,
435
+ onClick,
436
+ onMouseMove
301
437
  }) {
302
438
  const [error, setError] = useState(null);
303
- const [debugInfo, setDebugInfo] = useState({
304
- iResolution: [0, 0],
305
- iMouse: [0, 0]
306
- });
307
- const lastDebugUpdateRef = useRef2(0);
308
439
  const handleError = useCallback2((err) => {
309
440
  setError(err.message);
310
441
  console.error("ReactShader error:", err);
@@ -313,17 +444,7 @@ function ReactShader({
313
444
  if (onFrame) {
314
445
  onFrame(info);
315
446
  }
316
- if (!debug)
317
- return;
318
- const now = performance.now();
319
- if (now - lastDebugUpdateRef.current < 100)
320
- return;
321
- lastDebugUpdateRef.current = now;
322
- setDebugInfo({
323
- iResolution: info.resolution,
324
- iMouse: info.mouse
325
- });
326
- }, [debug, onFrame]);
447
+ }, [onFrame]);
327
448
  useEffect2(() => {
328
449
  setError(null);
329
450
  }, [fragment, vertex]);
@@ -333,6 +454,8 @@ function ReactShader({
333
454
  uniforms,
334
455
  onError: handleError,
335
456
  onFrame: handleFrame,
457
+ onClick,
458
+ onMouseMove,
336
459
  timeScale
337
460
  });
338
461
  const containerStyle = fullscreen ? {
@@ -361,7 +484,9 @@ function ReactShader({
361
484
  fontSize: "12px",
362
485
  padding: "16px",
363
486
  overflow: "auto",
364
- boxSizing: "border-box"
487
+ boxSizing: "border-box",
488
+ width: "100%",
489
+ height: "100%"
365
490
  },
366
491
  children: /* @__PURE__ */ jsxDEV("pre", {
367
492
  style: { margin: 0, whiteSpace: "pre-wrap" },
@@ -369,51 +494,13 @@ function ReactShader({
369
494
  }, undefined, false, undefined, this)
370
495
  }, undefined, false, undefined, this);
371
496
  }
372
- return /* @__PURE__ */ jsxDEV("div", {
373
- style: containerStyle,
374
- children: [
375
- /* @__PURE__ */ jsxDEV("canvas", {
376
- ref: canvasRef,
377
- className,
378
- style: { display: "block", width: "100%", height: "100%" }
379
- }, undefined, false, undefined, this),
380
- debug && /* @__PURE__ */ jsxDEV("div", {
381
- style: {
382
- position: "absolute",
383
- top: 8,
384
- left: 8,
385
- backgroundColor: "rgba(0, 0, 0, 0.7)",
386
- color: "#fff",
387
- fontFamily: "monospace",
388
- fontSize: "12px",
389
- padding: "8px",
390
- borderRadius: "4px",
391
- pointerEvents: "none"
392
- },
393
- children: [
394
- /* @__PURE__ */ jsxDEV("div", {
395
- children: [
396
- "iResolution: [",
397
- debugInfo.iResolution[0],
398
- ", ",
399
- debugInfo.iResolution[1],
400
- "]"
401
- ]
402
- }, undefined, true, undefined, this),
403
- /* @__PURE__ */ jsxDEV("div", {
404
- children: [
405
- "iMouse: [",
406
- Math.round(debugInfo.iMouse[0]),
407
- ", ",
408
- Math.round(debugInfo.iMouse[1]),
409
- "]"
410
- ]
411
- }, undefined, true, undefined, this)
412
- ]
413
- }, undefined, true, undefined, this)
414
- ]
415
- }, undefined, true, undefined, this);
497
+ return /* @__PURE__ */ jsxDEV("canvas", {
498
+ ref: canvasRef,
499
+ className,
500
+ style: { display: "block", width: "100%", height: "100%" }
501
+ }, undefined, false, undefined, this);
416
502
  }
417
503
  export {
504
+ generateUniformDeclarations,
418
505
  ReactShader
419
506
  };
package/dist/types.d.ts CHANGED
@@ -2,20 +2,26 @@ import type { FrameInfo } from "./hooks/useWebGL";
2
2
  export type Vec2 = [number, number];
3
3
  export type Vec3 = [number, number, number];
4
4
  export type Vec4 = [number, number, number, number];
5
- export type UniformValue = number | Vec2 | Vec3 | Vec4;
5
+ export type FloatArray = number[];
6
+ export type Vec2Array = Vec2[];
7
+ export type Vec3Array = Vec3[];
8
+ export type Vec4Array = Vec4[];
9
+ export type UniformValue = number | Vec2 | Vec3 | Vec4 | FloatArray | Vec2Array | Vec3Array | Vec4Array;
6
10
  export interface ReactShaderProps {
7
11
  className?: string;
8
12
  fragment: string;
9
13
  vertex?: string;
10
14
  uniforms?: Record<string, UniformValue>;
11
- debug?: boolean;
12
15
  fullscreen?: boolean;
13
16
  timeScale?: number;
14
17
  onFrame?: (info: FrameInfo) => void;
18
+ onClick?: (info: FrameInfo) => void;
19
+ onMouseMove?: (info: FrameInfo) => void;
15
20
  }
16
21
  export interface DefaultUniforms {
17
22
  iTime: number;
18
23
  iMouse: Vec2;
24
+ iMouseNormalized: Vec2;
19
25
  iMouseLeftDown: number;
20
26
  iResolution: Vec2;
21
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACnC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,IAAI,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,IAAI,CAAA;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACnC,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAC3C,MAAM,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAEnD,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAA;AACjC,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAC9B,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAC9B,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,CAAA;AAE9B,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAA;AAEvG,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,IAAI,CAAA;IACZ,gBAAgB,EAAE,IAAI,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,IAAI,CAAA;CAClB"}
@@ -1,8 +1,11 @@
1
1
  import type { UniformValue } from "../types";
2
2
  type WebGLContext = WebGLRenderingContext | WebGL2RenderingContext;
3
+ export declare const MAX_ARRAY_LENGTH = 100;
3
4
  export declare function setUniform(gl: WebGLContext, location: WebGLUniformLocation | null, value: UniformValue): void;
4
5
  export declare function getUniformLocation(gl: WebGLContext, program: WebGLProgram, name: string): WebGLUniformLocation | null;
5
6
  export declare function setUniforms(gl: WebGLContext, program: WebGLProgram, uniforms: Record<string, UniformValue>, locationCache: Map<string, WebGLUniformLocation | null>): void;
6
7
  export declare function createUniformLocationCache(): Map<string, WebGLUniformLocation | null>;
8
+ export declare function generateUniformDeclarations(uniforms: Record<string, UniformValue>): string;
9
+ export declare function injectUniformDeclarations(shaderSource: string, customUniforms: Record<string, UniformValue> | undefined, defaultUniforms: Record<string, UniformValue>): string;
7
10
  export {};
8
11
  //# sourceMappingURL=uniforms.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uniforms.d.ts","sourceRoot":"","sources":["../../src/utils/uniforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,UAAU,CAAA;AAE9D,KAAK,YAAY,GAAG,qBAAqB,GAAG,sBAAsB,CAAA;AAclE,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAc7G;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAErH;AAED,wBAAgB,WAAW,CACzB,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC,GACtD,IAAI,CASN;AAED,wBAAgB,0BAA0B,IAAI,GAAG,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC,CAErF"}
1
+ {"version":3,"file":"uniforms.d.ts","sourceRoot":"","sources":["../../src/utils/uniforms.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAqD,MAAM,UAAU,CAAA;AAE3G,KAAK,YAAY,GAAG,qBAAqB,GAAG,sBAAsB,CAAA;AAElE,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAkCnC,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAsB7G;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAErH;AAED,wBAAgB,WAAW,CACzB,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACtC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC,GACtD,IAAI,CAsBN;AAED,wBAAgB,0BAA0B,IAAI,GAAG,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI,CAAC,CAErF;AA8BD,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,MAAM,CAa1F;AAID,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,EACxD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAC5C,MAAM,CAQR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjandin/react-shader",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "React component for rendering WebGL shaders",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",