@actis/core 26.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +129 -0
- package/dist/index.cjs +343 -0
- package/dist/index.d.cts +117 -0
- package/dist/index.d.mts +117 -0
- package/dist/index.mjs +340 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# SOHNE | Actis
|
|
2
|
+
|
|
3
|
+
[![NPM version][npm-image]][npm-url]
|
|
4
|
+
[![PR Welcome][npm-downloads-image]][npm-downloads-url]
|
|
5
|
+
|
|
6
|
+
`Actis` is a lightweight WebGL rendering library designed to make it easy to work with WebGL fragment shaders, passes, and textures. It integrates seamlessly with React for modern web development.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Simple API for setting up WebGL rendering contexts
|
|
11
|
+
- Support for multiple rendering passes and shaders
|
|
12
|
+
- Integration with React for easy use in web applications
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
You can install `Actis` via npm:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @actis/core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
or via yarn:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
yarn add @actis/core
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Basic Usage
|
|
31
|
+
|
|
32
|
+
Here's a simple example to get you started:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import WebGLRenderer from '@actis/core'
|
|
36
|
+
import React, { useEffect, useRef } from 'react'
|
|
37
|
+
|
|
38
|
+
// Main React functional component
|
|
39
|
+
function App() {
|
|
40
|
+
const canvasRef = useRef<HTMLCanvasElement>(null) // Reference to the canvas element
|
|
41
|
+
const rendererRef = useRef<WebGLRenderer>() // Reference to the WebGL renderer
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
rendererRef.current = new WebGLRenderer(canvasRef.current) // Initialize the renderer with the canvas element
|
|
45
|
+
const passes = {
|
|
46
|
+
passes: [
|
|
47
|
+
{
|
|
48
|
+
name: 'bufferA',
|
|
49
|
+
fragmentShader: `
|
|
50
|
+
#ifdef GL_ES
|
|
51
|
+
precision mediump float;
|
|
52
|
+
#endif
|
|
53
|
+
|
|
54
|
+
uniform vec2 u_resolution;
|
|
55
|
+
uniform float u_time;
|
|
56
|
+
uniform vec2 u_mouse;
|
|
57
|
+
|
|
58
|
+
float sdCircle(in vec2 p, in float r) {
|
|
59
|
+
return length(p) - r;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
void main() {
|
|
63
|
+
vec2 p = (2. * gl_FragCoord.xy - u_resolution.xy) / u_resolution.y;
|
|
64
|
+
vec2 m = (2. * u_mouse.xy - u_resolution.xy) / u_resolution.y;
|
|
65
|
+
vec3 color = vec3(.0);
|
|
66
|
+
float d = sdCircle(p - m, .125);
|
|
67
|
+
color = mix(color, vec3(1.), 1.0 - smoothstep(0.0, 0.01, d));
|
|
68
|
+
gl_FragColor = vec4(color, 1.);
|
|
69
|
+
}
|
|
70
|
+
`,
|
|
71
|
+
textures: [],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'bufferB',
|
|
75
|
+
fragmentShader: `
|
|
76
|
+
precision highp float;
|
|
77
|
+
uniform sampler2D u_texture0;
|
|
78
|
+
uniform vec2 u_resolution;
|
|
79
|
+
void main() {
|
|
80
|
+
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
81
|
+
vec4 color = texture2D(u_texture0, uv);
|
|
82
|
+
float smoothValue = smoothstep(0.0, 1.0, color.r);
|
|
83
|
+
gl_FragColor = vec4(smoothValue, 0.0, 0.0, 1.0);
|
|
84
|
+
}
|
|
85
|
+
`,
|
|
86
|
+
textures: ['bufferA'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'MainBuffer',
|
|
90
|
+
fragmentShader: `
|
|
91
|
+
precision highp float;
|
|
92
|
+
uniform sampler2D u_texture0;
|
|
93
|
+
uniform vec2 u_resolution;
|
|
94
|
+
void main() {
|
|
95
|
+
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
96
|
+
vec4 color = texture2D(u_texture0, uv);
|
|
97
|
+
gl_FragColor = color;
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
textures: ['bufferB'],
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
textures: [],
|
|
104
|
+
}
|
|
105
|
+
rendererRef.current.setup(passes) // Setup the renderer with the passes
|
|
106
|
+
requestAnimationFrame(rendererRef.current.render) // Start the rendering loop
|
|
107
|
+
}, [])
|
|
108
|
+
|
|
109
|
+
// Render the canvas element
|
|
110
|
+
return <canvas ref={canvasRef} width={800} height={600} />
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default App
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Advanced Usage
|
|
117
|
+
|
|
118
|
+
For more advanced usage, such as adding multiple passes and using textures, refer to the [API documentation](#advanced-usage) ~in a near future~.
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
|
|
123
|
+
|
|
124
|
+
[//]: (Externals)
|
|
125
|
+
[npm-image]: https://img.shields.io/npm/v/@actis/core.svg?style=flat-square&logo=npm
|
|
126
|
+
[npm-url]: https://npmjs.org/package/@actis/core
|
|
127
|
+
[npm-downloads-image]: https://img.shields.io/npm/dm/@actis/core.svg
|
|
128
|
+
[npm-downloads-url]: https://npmcharts.com/compare/@actis/core?minimal=true
|
|
129
|
+
[//]: (EOF)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/pass/Pass.ts
|
|
3
|
+
/**
|
|
4
|
+
* Represents a render pass in the WebGL pipeline.
|
|
5
|
+
* Responsible for managing framebuffer, texture, and draw calls for a single pass.
|
|
6
|
+
*/
|
|
7
|
+
var Pass = class {
|
|
8
|
+
constructor(gl, shader, width, height, offscreen = true, textures = []) {
|
|
9
|
+
this.gl = gl;
|
|
10
|
+
this.shader = shader;
|
|
11
|
+
this.width = width;
|
|
12
|
+
this.height = height;
|
|
13
|
+
this.texture = this.createTexture();
|
|
14
|
+
this.framebuffer = this.createFramebuffer(this.texture);
|
|
15
|
+
this.next = null;
|
|
16
|
+
this.offscreen = offscreen;
|
|
17
|
+
this.textures = textures;
|
|
18
|
+
this.positionAttributeLocation = this.shader.getAttribLocation("a_position");
|
|
19
|
+
this.positionBuffer = this.createPositionBuffer();
|
|
20
|
+
}
|
|
21
|
+
use() {
|
|
22
|
+
this.shader.use();
|
|
23
|
+
if (this.offscreen) {
|
|
24
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.framebuffer);
|
|
25
|
+
this.gl.viewport(0, 0, this.width, this.height);
|
|
26
|
+
} else {
|
|
27
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
28
|
+
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
resize(width, height) {
|
|
32
|
+
this.width = width;
|
|
33
|
+
this.height = height;
|
|
34
|
+
if (this.offscreen) {
|
|
35
|
+
this.texture = this.createTexture();
|
|
36
|
+
this.framebuffer = this.createFramebuffer(this.texture);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
draw() {
|
|
40
|
+
const positions = new Float32Array([
|
|
41
|
+
-1,
|
|
42
|
+
-1,
|
|
43
|
+
1,
|
|
44
|
+
-1,
|
|
45
|
+
-1,
|
|
46
|
+
1,
|
|
47
|
+
-1,
|
|
48
|
+
1,
|
|
49
|
+
1,
|
|
50
|
+
-1,
|
|
51
|
+
1,
|
|
52
|
+
1
|
|
53
|
+
]);
|
|
54
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
|
|
55
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW);
|
|
56
|
+
this.gl.enableVertexAttribArray(this.positionAttributeLocation);
|
|
57
|
+
this.gl.vertexAttribPointer(this.positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);
|
|
58
|
+
this.textures.forEach((texture, index) => {
|
|
59
|
+
this.gl.activeTexture(this.gl.TEXTURE0 + index);
|
|
60
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
61
|
+
this.shader.setUniform(`u_texture${index}`, "1i", index);
|
|
62
|
+
});
|
|
63
|
+
this.gl.drawArrays(this.gl.TRIANGLES, 0, positions.length / 2);
|
|
64
|
+
}
|
|
65
|
+
createTexture() {
|
|
66
|
+
const texture = this.gl.createTexture();
|
|
67
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
68
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
|
69
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
|
|
70
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
|
|
71
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
|
|
72
|
+
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.width, this.height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null);
|
|
73
|
+
return texture;
|
|
74
|
+
}
|
|
75
|
+
createFramebuffer(texture) {
|
|
76
|
+
const framebuffer = this.gl.createFramebuffer();
|
|
77
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, framebuffer);
|
|
78
|
+
this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, texture, 0);
|
|
79
|
+
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
|
|
80
|
+
return framebuffer;
|
|
81
|
+
}
|
|
82
|
+
createPositionBuffer() {
|
|
83
|
+
const buffer = this.gl.createBuffer();
|
|
84
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
|
|
85
|
+
return buffer;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/shader/Shader.ts
|
|
90
|
+
const ERROR_LOG_REGEX = /ERROR: 0:(\d+): (.*)(?=\n|$)/;
|
|
91
|
+
/**
|
|
92
|
+
* Compiles and manages a WebGL shader program.
|
|
93
|
+
* Responsible for compiling, linking, and providing access to uniforms and attributes.
|
|
94
|
+
*/
|
|
95
|
+
var Shader = class {
|
|
96
|
+
constructor(gl, vertexSource, fragmentSource, onError, passName) {
|
|
97
|
+
this.gl = gl;
|
|
98
|
+
this.onError = onError;
|
|
99
|
+
this.passName = passName;
|
|
100
|
+
this.uniformLocations = /* @__PURE__ */ new Map();
|
|
101
|
+
const vertexShader = this.compileShader(vertexSource || this.defaultVertexShader(), gl.VERTEX_SHADER);
|
|
102
|
+
const fragmentShader = this.compileShader(fragmentSource, gl.FRAGMENT_SHADER);
|
|
103
|
+
this.program = this.linkProgram(vertexShader, fragmentShader);
|
|
104
|
+
}
|
|
105
|
+
compileShader(source, type) {
|
|
106
|
+
const shader = this.gl.createShader(type);
|
|
107
|
+
if (!shader) throw new Error("Failed to create shader");
|
|
108
|
+
this.gl.shaderSource(shader, source);
|
|
109
|
+
this.gl.compileShader(shader);
|
|
110
|
+
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
|
111
|
+
const log = this.gl.getShaderInfoLog(shader);
|
|
112
|
+
this.gl.deleteShader(shader);
|
|
113
|
+
const coords = this.extractErrorCoords(log || "");
|
|
114
|
+
this.onError({
|
|
115
|
+
passName: this.passName,
|
|
116
|
+
coords
|
|
117
|
+
});
|
|
118
|
+
throw new Error(`Failed to compile shader: ${log}`);
|
|
119
|
+
}
|
|
120
|
+
return shader;
|
|
121
|
+
}
|
|
122
|
+
extractErrorCoords(log) {
|
|
123
|
+
const match = ERROR_LOG_REGEX.exec(log);
|
|
124
|
+
if (match) return {
|
|
125
|
+
line: Number.parseInt(match[1], 10),
|
|
126
|
+
message: match[2]
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
line: 0,
|
|
130
|
+
message: ""
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
linkProgram(vertexShader, fragmentShader) {
|
|
134
|
+
const program = this.gl.createProgram();
|
|
135
|
+
if (!program) throw new Error("Failed to create program");
|
|
136
|
+
if (vertexShader) this.gl.attachShader(program, vertexShader);
|
|
137
|
+
this.gl.attachShader(program, fragmentShader);
|
|
138
|
+
this.gl.linkProgram(program);
|
|
139
|
+
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
|
|
140
|
+
const log = this.gl.getProgramInfoLog(program);
|
|
141
|
+
this.gl.deleteProgram(program);
|
|
142
|
+
throw new Error(`Failed to link program: ${log}`);
|
|
143
|
+
}
|
|
144
|
+
return program;
|
|
145
|
+
}
|
|
146
|
+
use() {
|
|
147
|
+
this.gl.useProgram(this.program);
|
|
148
|
+
}
|
|
149
|
+
setUniform(name, type, ...values) {
|
|
150
|
+
let location = this.uniformLocations.get(name);
|
|
151
|
+
if (!location) {
|
|
152
|
+
location = this.gl.getUniformLocation(this.program, name);
|
|
153
|
+
this.uniformLocations.set(name, location);
|
|
154
|
+
}
|
|
155
|
+
switch (type) {
|
|
156
|
+
case "1f":
|
|
157
|
+
this.gl.uniform1f(location, values[0]);
|
|
158
|
+
break;
|
|
159
|
+
case "1i":
|
|
160
|
+
this.gl.uniform1i(location, values[0]);
|
|
161
|
+
break;
|
|
162
|
+
case "2fv":
|
|
163
|
+
this.gl.uniform2fv(location, values[0]);
|
|
164
|
+
break;
|
|
165
|
+
case "3fv":
|
|
166
|
+
this.gl.uniform3fv(location, values[0]);
|
|
167
|
+
break;
|
|
168
|
+
case "4fv":
|
|
169
|
+
this.gl.uniform4fv(location, values[0]);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
getAttribLocation(name) {
|
|
174
|
+
return this.gl.getAttribLocation(this.program, name);
|
|
175
|
+
}
|
|
176
|
+
defaultVertexShader() {
|
|
177
|
+
return `
|
|
178
|
+
attribute vec4 a_position;
|
|
179
|
+
void main() {
|
|
180
|
+
gl_Position = a_position;
|
|
181
|
+
}`;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
//#endregion
|
|
185
|
+
//#region src/engine/WebGLRenderer.ts
|
|
186
|
+
/**
|
|
187
|
+
* Renderer class responsible for managing the WebGL context, render passes, and animation loop.
|
|
188
|
+
*/
|
|
189
|
+
var WebGLRenderer = class {
|
|
190
|
+
constructor(canvas, onError) {
|
|
191
|
+
this.canvas = canvas;
|
|
192
|
+
this.animationRequestID = -1;
|
|
193
|
+
this.gl = this.initializeWebGLContext(canvas);
|
|
194
|
+
this.passes = null;
|
|
195
|
+
this.textureMap = /* @__PURE__ */ new Map();
|
|
196
|
+
this.now = /* @__PURE__ */ new Date();
|
|
197
|
+
this.onError = onError;
|
|
198
|
+
this.mouseX = 0;
|
|
199
|
+
this.mouseY = 0;
|
|
200
|
+
this.time = 0;
|
|
201
|
+
this.timeDelta = 0;
|
|
202
|
+
this.realToCSSPixels = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
203
|
+
this.paused = false;
|
|
204
|
+
this.playbackTime = 0;
|
|
205
|
+
this.lastTime = 0;
|
|
206
|
+
this.frameRate = 0;
|
|
207
|
+
this.currentFrame = 0;
|
|
208
|
+
this.currentTime = 0;
|
|
209
|
+
this.startTime = 0;
|
|
210
|
+
this.initMouseEvents();
|
|
211
|
+
if (typeof window !== "undefined") window.addEventListener("resize", this.resizeCanvasToDisplaySize.bind(this));
|
|
212
|
+
}
|
|
213
|
+
initializeWebGLContext(canvas) {
|
|
214
|
+
const opts = {
|
|
215
|
+
alpha: false,
|
|
216
|
+
depth: false,
|
|
217
|
+
stencil: false,
|
|
218
|
+
premultipliedAlpha: false,
|
|
219
|
+
antialias: false,
|
|
220
|
+
preserveDrawingBuffer: true,
|
|
221
|
+
powerPreference: "high-performance"
|
|
222
|
+
};
|
|
223
|
+
const gl = canvas.getContext("webgl2", opts) || canvas.getContext("experimental-webgl2", opts) || canvas.getContext("webgl", opts) || canvas.getContext("experimental-webgl", opts);
|
|
224
|
+
if (!gl) throw new Error("WebGL not supported");
|
|
225
|
+
return gl;
|
|
226
|
+
}
|
|
227
|
+
initMouseEvents() {
|
|
228
|
+
this.canvas.addEventListener("mousemove", this.setMousePosition.bind(this));
|
|
229
|
+
this.canvas.addEventListener("touchstart", this.preventDefault, { passive: false });
|
|
230
|
+
this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this), { passive: false });
|
|
231
|
+
}
|
|
232
|
+
setMousePosition(e) {
|
|
233
|
+
const mouse = {
|
|
234
|
+
x: e.clientX || e.pageX,
|
|
235
|
+
y: e.clientY || e.pageY
|
|
236
|
+
};
|
|
237
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
238
|
+
if (mouse.x >= rect.left && mouse.x <= rect.right && mouse.y >= rect.top && mouse.y <= rect.bottom) {
|
|
239
|
+
this.mouseX = (mouse.x - rect.left) * this.realToCSSPixels;
|
|
240
|
+
this.mouseY = this.canvas.height - (mouse.y - rect.top) * this.realToCSSPixels;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
handleTouchMove(e) {
|
|
244
|
+
e.preventDefault();
|
|
245
|
+
if (e.touches.length > 0) this.setMousePosition(e.touches[0]);
|
|
246
|
+
}
|
|
247
|
+
preventDefault(e) {
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
}
|
|
250
|
+
addPass(pass) {
|
|
251
|
+
if (this.passes) {
|
|
252
|
+
let current = this.passes;
|
|
253
|
+
while (current.next) current = current.next;
|
|
254
|
+
current.next = pass;
|
|
255
|
+
} else this.passes = pass;
|
|
256
|
+
}
|
|
257
|
+
resizeCanvasToDisplaySize() {
|
|
258
|
+
const displayWidth = Math.floor(this.canvas.clientWidth * this.realToCSSPixels);
|
|
259
|
+
const displayHeight = Math.floor(this.canvas.clientHeight * this.realToCSSPixels);
|
|
260
|
+
if (this.canvas.width !== displayWidth || this.canvas.height !== displayHeight) {
|
|
261
|
+
this.canvas.width = displayWidth;
|
|
262
|
+
this.canvas.height = displayHeight;
|
|
263
|
+
let current = this.passes;
|
|
264
|
+
while (current) {
|
|
265
|
+
current.resize(displayWidth, displayHeight);
|
|
266
|
+
current = current.next;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
updateUniforms(pass) {
|
|
271
|
+
pass.shader.setUniform("u_date", "4fv", [
|
|
272
|
+
this.now.getFullYear(),
|
|
273
|
+
this.now.getMonth() + 1,
|
|
274
|
+
this.now.getDate(),
|
|
275
|
+
this.now.getHours() * 3600 + this.now.getMinutes() * 60 + this.now.getSeconds() + this.now.getMilliseconds() / 1e3
|
|
276
|
+
]);
|
|
277
|
+
pass.shader.setUniform("u_frame", "1i", this.currentFrame);
|
|
278
|
+
pass.shader.setUniform("u_time", "1f", this.currentTime);
|
|
279
|
+
pass.shader.setUniform("u_timeDelta", "1f", this.timeDelta);
|
|
280
|
+
pass.shader.setUniform("u_frameRate", "1f", this.frameRate);
|
|
281
|
+
pass.shader.setUniform("u_resolution", "2fv", [pass.width, pass.height]);
|
|
282
|
+
pass.shader.setUniform("u_mouse", "2fv", [this.mouseX, this.mouseY]);
|
|
283
|
+
}
|
|
284
|
+
updateTime(currentTime) {
|
|
285
|
+
if (this.paused) {
|
|
286
|
+
this.timeDelta = 0;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const t = currentTime ?? (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
290
|
+
if (this.startTime === 0) this.startTime = t;
|
|
291
|
+
if (this.lastTime === 0) this.lastTime = t;
|
|
292
|
+
this.timeDelta = (t - this.startTime) / 1e3;
|
|
293
|
+
this.currentTime += this.timeDelta;
|
|
294
|
+
this.frameRate = 1 / this.timeDelta;
|
|
295
|
+
this.currentFrame++;
|
|
296
|
+
this.startTime = t;
|
|
297
|
+
}
|
|
298
|
+
render(currentTime) {
|
|
299
|
+
this.updateTime(currentTime);
|
|
300
|
+
this.resizeCanvasToDisplaySize();
|
|
301
|
+
let current = this.passes;
|
|
302
|
+
while (current) {
|
|
303
|
+
current.use();
|
|
304
|
+
this.updateUniforms(current);
|
|
305
|
+
current.draw();
|
|
306
|
+
current = current.next;
|
|
307
|
+
}
|
|
308
|
+
if (typeof requestAnimationFrame !== "undefined") this.animationRequestID = requestAnimationFrame(this.render.bind(this));
|
|
309
|
+
}
|
|
310
|
+
setup(config) {
|
|
311
|
+
const displayWidth = Math.floor(this.canvas.clientWidth * this.realToCSSPixels);
|
|
312
|
+
const displayHeight = Math.floor(this.canvas.clientHeight * this.realToCSSPixels);
|
|
313
|
+
config.passes.forEach((passConfig) => {
|
|
314
|
+
try {
|
|
315
|
+
const shader = new Shader(this.gl, passConfig.vertexShader, passConfig.fragmentShader, this.onError, passConfig.name);
|
|
316
|
+
const offscreen = passConfig.offscreen || passConfig.name !== "MainBuffer";
|
|
317
|
+
const pass = new Pass(this.gl, shader, displayWidth, displayHeight, offscreen, passConfig.textures.map((textureName) => this.textureMap.get(textureName)));
|
|
318
|
+
this.addPass(pass);
|
|
319
|
+
this.textureMap.set(passConfig.name, pass.texture);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error(`Error in pass ${passConfig.name}: ${error.message}`);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
play() {
|
|
326
|
+
this.paused = false;
|
|
327
|
+
this.animationRequestID = requestAnimationFrame(this.render.bind(this));
|
|
328
|
+
}
|
|
329
|
+
pause() {
|
|
330
|
+
this.paused = true;
|
|
331
|
+
cancelAnimationFrame(this.animationRequestID);
|
|
332
|
+
}
|
|
333
|
+
reset() {
|
|
334
|
+
this.playbackTime = 0;
|
|
335
|
+
this.currentFrame = 0;
|
|
336
|
+
this.lastTime = 0;
|
|
337
|
+
this.frameRate = 0;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
exports.Pass = Pass;
|
|
342
|
+
exports.Shader = Shader;
|
|
343
|
+
exports.WebGLRenderer = WebGLRenderer;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/types/renderer.d.ts
|
|
2
|
+
type PassConfig = {
|
|
3
|
+
name: string;
|
|
4
|
+
fragmentShader: string;
|
|
5
|
+
vertexShader?: string;
|
|
6
|
+
offscreen?: boolean;
|
|
7
|
+
textures: string[];
|
|
8
|
+
};
|
|
9
|
+
type RendererConfig = {
|
|
10
|
+
passes: PassConfig[];
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/types/shader.d.ts
|
|
14
|
+
type ShaderError = {
|
|
15
|
+
passName: string;
|
|
16
|
+
coords: {
|
|
17
|
+
line: number;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
type UniformType = '1f' | '1i' | '2fv' | '3fv' | '4fv';
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/shader/Shader.d.ts
|
|
24
|
+
/**
|
|
25
|
+
* Compiles and manages a WebGL shader program.
|
|
26
|
+
* Responsible for compiling, linking, and providing access to uniforms and attributes.
|
|
27
|
+
*/
|
|
28
|
+
declare class Shader {
|
|
29
|
+
private gl;
|
|
30
|
+
private program;
|
|
31
|
+
private uniformLocations;
|
|
32
|
+
private onError;
|
|
33
|
+
private passName;
|
|
34
|
+
constructor(gl: WebGLRenderingContext, vertexSource: string | undefined, fragmentSource: string, onError: (details: ShaderError) => void, passName: string);
|
|
35
|
+
private compileShader;
|
|
36
|
+
private extractErrorCoords;
|
|
37
|
+
private linkProgram;
|
|
38
|
+
use(): void;
|
|
39
|
+
setUniform(name: string, type: UniformType, ...values: any[]): void;
|
|
40
|
+
getAttribLocation(name: string): number;
|
|
41
|
+
private defaultVertexShader;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/pass/Pass.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Represents a render pass in the WebGL pipeline.
|
|
47
|
+
* Responsible for managing framebuffer, texture, and draw calls for a single pass.
|
|
48
|
+
*/
|
|
49
|
+
declare class Pass {
|
|
50
|
+
gl: WebGLRenderingContext;
|
|
51
|
+
shader: Shader;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
texture: WebGLTexture;
|
|
55
|
+
framebuffer: WebGLFramebuffer;
|
|
56
|
+
next: Pass | null;
|
|
57
|
+
offscreen: boolean;
|
|
58
|
+
textures: WebGLTexture[];
|
|
59
|
+
positionBuffer: WebGLBuffer;
|
|
60
|
+
positionAttributeLocation: number;
|
|
61
|
+
constructor(gl: WebGLRenderingContext, shader: Shader, width: number, height: number, offscreen?: boolean, textures?: WebGLTexture[]);
|
|
62
|
+
use(): void;
|
|
63
|
+
resize(width: number, height: number): void;
|
|
64
|
+
draw(): void;
|
|
65
|
+
private createTexture;
|
|
66
|
+
private createFramebuffer;
|
|
67
|
+
private createPositionBuffer;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/engine/WebGLRenderer.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Renderer class responsible for managing the WebGL context, render passes, and animation loop.
|
|
73
|
+
*/
|
|
74
|
+
declare class WebGLRenderer {
|
|
75
|
+
private gl;
|
|
76
|
+
private passes;
|
|
77
|
+
private canvas;
|
|
78
|
+
private animationRequestID;
|
|
79
|
+
private textureMap;
|
|
80
|
+
private now;
|
|
81
|
+
private onError;
|
|
82
|
+
mouseX: number;
|
|
83
|
+
mouseY: number;
|
|
84
|
+
time: number;
|
|
85
|
+
timeDelta: number;
|
|
86
|
+
realToCSSPixels: number;
|
|
87
|
+
paused: boolean;
|
|
88
|
+
playbackTime: number;
|
|
89
|
+
lastTime: number;
|
|
90
|
+
frameRate: number;
|
|
91
|
+
currentFrame: number;
|
|
92
|
+
currentTime: number;
|
|
93
|
+
startTime: number;
|
|
94
|
+
constructor(canvas: HTMLCanvasElement, onError: (details: {
|
|
95
|
+
passName: string;
|
|
96
|
+
coords: {
|
|
97
|
+
line: number;
|
|
98
|
+
message: string;
|
|
99
|
+
};
|
|
100
|
+
}) => void);
|
|
101
|
+
private initializeWebGLContext;
|
|
102
|
+
private initMouseEvents;
|
|
103
|
+
private setMousePosition;
|
|
104
|
+
private handleTouchMove;
|
|
105
|
+
private preventDefault;
|
|
106
|
+
addPass(pass: Pass): void;
|
|
107
|
+
private resizeCanvasToDisplaySize;
|
|
108
|
+
private updateUniforms;
|
|
109
|
+
private updateTime;
|
|
110
|
+
render(currentTime: number): void;
|
|
111
|
+
setup(config: RendererConfig): void;
|
|
112
|
+
play(): void;
|
|
113
|
+
pause(): void;
|
|
114
|
+
reset(): void;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { Pass, type PassConfig, type RendererConfig, Shader, WebGLRenderer };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/types/renderer.d.ts
|
|
2
|
+
type PassConfig = {
|
|
3
|
+
name: string;
|
|
4
|
+
fragmentShader: string;
|
|
5
|
+
vertexShader?: string;
|
|
6
|
+
offscreen?: boolean;
|
|
7
|
+
textures: string[];
|
|
8
|
+
};
|
|
9
|
+
type RendererConfig = {
|
|
10
|
+
passes: PassConfig[];
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/types/shader.d.ts
|
|
14
|
+
type ShaderError = {
|
|
15
|
+
passName: string;
|
|
16
|
+
coords: {
|
|
17
|
+
line: number;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
type UniformType = '1f' | '1i' | '2fv' | '3fv' | '4fv';
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/shader/Shader.d.ts
|
|
24
|
+
/**
|
|
25
|
+
* Compiles and manages a WebGL shader program.
|
|
26
|
+
* Responsible for compiling, linking, and providing access to uniforms and attributes.
|
|
27
|
+
*/
|
|
28
|
+
declare class Shader {
|
|
29
|
+
private gl;
|
|
30
|
+
private program;
|
|
31
|
+
private uniformLocations;
|
|
32
|
+
private onError;
|
|
33
|
+
private passName;
|
|
34
|
+
constructor(gl: WebGLRenderingContext, vertexSource: string | undefined, fragmentSource: string, onError: (details: ShaderError) => void, passName: string);
|
|
35
|
+
private compileShader;
|
|
36
|
+
private extractErrorCoords;
|
|
37
|
+
private linkProgram;
|
|
38
|
+
use(): void;
|
|
39
|
+
setUniform(name: string, type: UniformType, ...values: any[]): void;
|
|
40
|
+
getAttribLocation(name: string): number;
|
|
41
|
+
private defaultVertexShader;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/pass/Pass.d.ts
|
|
45
|
+
/**
|
|
46
|
+
* Represents a render pass in the WebGL pipeline.
|
|
47
|
+
* Responsible for managing framebuffer, texture, and draw calls for a single pass.
|
|
48
|
+
*/
|
|
49
|
+
declare class Pass {
|
|
50
|
+
gl: WebGLRenderingContext;
|
|
51
|
+
shader: Shader;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
texture: WebGLTexture;
|
|
55
|
+
framebuffer: WebGLFramebuffer;
|
|
56
|
+
next: Pass | null;
|
|
57
|
+
offscreen: boolean;
|
|
58
|
+
textures: WebGLTexture[];
|
|
59
|
+
positionBuffer: WebGLBuffer;
|
|
60
|
+
positionAttributeLocation: number;
|
|
61
|
+
constructor(gl: WebGLRenderingContext, shader: Shader, width: number, height: number, offscreen?: boolean, textures?: WebGLTexture[]);
|
|
62
|
+
use(): void;
|
|
63
|
+
resize(width: number, height: number): void;
|
|
64
|
+
draw(): void;
|
|
65
|
+
private createTexture;
|
|
66
|
+
private createFramebuffer;
|
|
67
|
+
private createPositionBuffer;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/engine/WebGLRenderer.d.ts
|
|
71
|
+
/**
|
|
72
|
+
* Renderer class responsible for managing the WebGL context, render passes, and animation loop.
|
|
73
|
+
*/
|
|
74
|
+
declare class WebGLRenderer {
|
|
75
|
+
private gl;
|
|
76
|
+
private passes;
|
|
77
|
+
private canvas;
|
|
78
|
+
private animationRequestID;
|
|
79
|
+
private textureMap;
|
|
80
|
+
private now;
|
|
81
|
+
private onError;
|
|
82
|
+
mouseX: number;
|
|
83
|
+
mouseY: number;
|
|
84
|
+
time: number;
|
|
85
|
+
timeDelta: number;
|
|
86
|
+
realToCSSPixels: number;
|
|
87
|
+
paused: boolean;
|
|
88
|
+
playbackTime: number;
|
|
89
|
+
lastTime: number;
|
|
90
|
+
frameRate: number;
|
|
91
|
+
currentFrame: number;
|
|
92
|
+
currentTime: number;
|
|
93
|
+
startTime: number;
|
|
94
|
+
constructor(canvas: HTMLCanvasElement, onError: (details: {
|
|
95
|
+
passName: string;
|
|
96
|
+
coords: {
|
|
97
|
+
line: number;
|
|
98
|
+
message: string;
|
|
99
|
+
};
|
|
100
|
+
}) => void);
|
|
101
|
+
private initializeWebGLContext;
|
|
102
|
+
private initMouseEvents;
|
|
103
|
+
private setMousePosition;
|
|
104
|
+
private handleTouchMove;
|
|
105
|
+
private preventDefault;
|
|
106
|
+
addPass(pass: Pass): void;
|
|
107
|
+
private resizeCanvasToDisplaySize;
|
|
108
|
+
private updateUniforms;
|
|
109
|
+
private updateTime;
|
|
110
|
+
render(currentTime: number): void;
|
|
111
|
+
setup(config: RendererConfig): void;
|
|
112
|
+
play(): void;
|
|
113
|
+
pause(): void;
|
|
114
|
+
reset(): void;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { Pass, type PassConfig, type RendererConfig, Shader, WebGLRenderer };
|