@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 +87 -4
- package/dist/ReactShader.d.ts +1 -1
- package/dist/ReactShader.d.ts.map +1 -1
- package/dist/example/frontend.d.ts.map +1 -1
- package/dist/example/glsl/cartesian-to-polar.glsl.d.ts +2 -0
- package/dist/example/glsl/cartesian-to-polar.glsl.d.ts.map +1 -0
- package/dist/example/lib/logger.d.ts +2 -0
- package/dist/example/lib/logger.d.ts.map +1 -0
- package/dist/example/shader.d.ts.map +1 -1
- package/dist/hooks/useWebGL.d.ts +4 -0
- package/dist/hooks/useWebGL.d.ts.map +1 -1
- package/dist/index.cjs +157 -70
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +158 -71
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/uniforms.d.ts +3 -0
- package/dist/utils/uniforms.d.ts.map +1 -1
- package/package.json +1 -1
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,
|
|
77
|
-
offset: [0.5, 0.5],
|
|
78
|
-
color: [1.0, 0.5, 0.2],
|
|
79
|
-
transform: [1, 0, 0, 1],
|
|
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
|
package/dist/ReactShader.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ReactShaderProps } from "./types";
|
|
2
|
-
export declare function ReactShader({ className, fragment, vertex, uniforms,
|
|
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,
|
|
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;
|
|
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 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"shader.d.ts","sourceRoot":"","sources":["../../src/example/shader.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,QA4FnB,CAAA"}
|
package/dist/hooks/useWebGL.d.ts
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
|
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
|
}
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/utils/uniforms.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|