@bigmistqke/view.gl 0.1.0 → 0.1.2
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/.claude/settings.local.json +2 -1
- package/.github/ISSUE_TEMPLATE/bug-report.yml +31 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +27 -0
- package/.github/workflows/codeql.yml +46 -0
- package/.github/workflows/deploy-page.yml +38 -0
- package/.github/workflows/format.yml +40 -0
- package/README.md +92 -35
- package/dev/404.html +19 -0
- package/dev/dist/404.html +19 -0
- package/dev/dist/assets/bezier-DuLTb2d1.js +294 -0
- package/dev/dist/assets/game_of_life-D7qHdFcv.js +202 -0
- package/dev/dist/assets/grid-Ie22U_9K.js +173 -0
- package/dev/dist/assets/index-0qrXMlfv.js +109 -0
- package/dev/dist/assets/index-C2N3u8wj.css +12 -0
- package/dev/dist/assets/main-BZ1N6pp6.js +32 -0
- package/dev/dist/assets/overview-DAxtMIw5.js +78 -0
- package/dev/dist/assets/pix_sim-DIhzM_vu.js +501 -0
- package/dev/dist/assets/symbol-BouRL-Ni.js +65 -0
- package/dev/dist/assets/tag-C_bg62fD.js +516 -0
- package/dev/dist/assets/tetris-IMnkSi9p.js +344 -0
- package/dev/dist/assets/utils-CvCgsM08.js +50 -0
- package/dev/dist/index.html +51 -0
- package/dev/index.html +39 -10
- package/dev/{bezier.ts → src/examples/bezier.ts} +1 -1
- package/dev/{grid.ts → src/examples/grid.ts} +1 -1
- package/dev/{pix_sim.ts → src/examples/pix_sim.ts} +112 -97
- package/dev/src/examples/symbol.ts +79 -0
- package/dev/{tetris.ts → src/examples/tetris.ts} +1 -0
- package/dev/src/index.css +12 -0
- package/dev/src/index.ts +69 -0
- package/dev/{utils.ts → src/utils.ts} +8 -2
- package/dev/vite.config.ts +9 -1
- package/dist/index.d.mts +18 -0
- package/dist/index.mjs +324 -0
- package/dist/index.mjs.map +1 -0
- package/dist/tag.d.mts +97 -0
- package/dist/tag.mjs +162 -0
- package/dist/tag.mjs.map +1 -0
- package/dist/types-DoFAJoh-.d.mts +266 -0
- package/package.json +24 -10
- package/src/index.ts +39 -17
- package/src/tag.ts +47 -39
- package/src/types.ts +24 -24
- package/src/{utils.ts → utils/index.ts} +39 -14
- package/src/utils/object.ts +27 -0
- package/test/index.test.ts +1017 -0
- package/test/mocks/webgl.ts +248 -0
- package/test/setup.ts +13 -0
- package/test/tag.test.ts +790 -0
- package/vitest.config.ts +15 -0
- /package/dev/{game_of_life.ts → src/examples/game_of_life.ts} +0 -0
- /package/dev/{index.ts → src/examples/overview.ts} +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { i as interleave, u as uniform, a as attribute, g as glsl, c as compile, v as view } from './tag-C_bg62fD.js';
|
|
2
|
+
import { c as createElement, a as cursor } from './utils-CvCgsM08.js';
|
|
3
|
+
|
|
4
|
+
const canvas = createElement("canvas");
|
|
5
|
+
canvas.width = window.innerWidth;
|
|
6
|
+
canvas.height = window.innerHeight;
|
|
7
|
+
const gl = canvas.getContext("webgl2", { antialias: true });
|
|
8
|
+
if (!gl) {
|
|
9
|
+
throw new Error("WebGL not supported");
|
|
10
|
+
}
|
|
11
|
+
const state = {
|
|
12
|
+
position: { x: 0, y: 0 },
|
|
13
|
+
// Pan offset in screen pixels
|
|
14
|
+
scale: 1,
|
|
15
|
+
// Zoom level
|
|
16
|
+
controlPoints: [
|
|
17
|
+
{ x: 200, y: 400 },
|
|
18
|
+
// P0: Start point of the bezier curve
|
|
19
|
+
{ x: 400, y: 100 },
|
|
20
|
+
// P1: Control point (influences curvature)
|
|
21
|
+
{ x: 600, y: 400 }
|
|
22
|
+
// P2: End point of the bezier curve
|
|
23
|
+
],
|
|
24
|
+
dragIndex: -1
|
|
25
|
+
// Index of control point being dragged (-1 = none)
|
|
26
|
+
};
|
|
27
|
+
function screenToWorld(screenX, screenY) {
|
|
28
|
+
return {
|
|
29
|
+
x: (screenX - state.position.x) / state.scale,
|
|
30
|
+
y: (screenY - state.position.y) / state.scale
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function worldToScreen(worldX, worldY) {
|
|
34
|
+
return {
|
|
35
|
+
x: worldX * state.scale + state.position.x,
|
|
36
|
+
y: worldY * state.scale + state.position.y
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function getControlPointAt(x, y, threshold = 20) {
|
|
40
|
+
for (let i = 0; i < state.controlPoints.length; i++) {
|
|
41
|
+
const screen = worldToScreen(state.controlPoints[i].x, state.controlPoints[i].y);
|
|
42
|
+
const dx = x - screen.x;
|
|
43
|
+
const dy = y - screen.y;
|
|
44
|
+
if (dx * dx + dy * dy < threshold * threshold) {
|
|
45
|
+
return i;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return -1;
|
|
49
|
+
}
|
|
50
|
+
canvas.addEventListener("pointerdown", (event) => {
|
|
51
|
+
const hitIndex = getControlPointAt(event.clientX, event.clientY);
|
|
52
|
+
if (hitIndex >= 0) {
|
|
53
|
+
state.dragIndex = hitIndex;
|
|
54
|
+
cursor(event, (event2) => {
|
|
55
|
+
const world = screenToWorld(event2.clientX, event2.clientY);
|
|
56
|
+
state.controlPoints[state.dragIndex] = world;
|
|
57
|
+
render();
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
cursor(event, (event2) => {
|
|
61
|
+
state.position.x -= event2.deltaX;
|
|
62
|
+
state.position.y -= event2.deltaY;
|
|
63
|
+
render();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
canvas.addEventListener(
|
|
68
|
+
"wheel",
|
|
69
|
+
(event) => {
|
|
70
|
+
const rect = canvas.getBoundingClientRect();
|
|
71
|
+
const mouseX = event.clientX - rect.left;
|
|
72
|
+
const mouseY = event.clientY - rect.top;
|
|
73
|
+
const scaleFactor = event.deltaY > 0 ? 0.95 : 1.05;
|
|
74
|
+
const oldScale = state.scale;
|
|
75
|
+
const newScale = Math.max(0.1, Math.min(10, oldScale * scaleFactor));
|
|
76
|
+
const worldX = (mouseX - state.position.x) / oldScale;
|
|
77
|
+
const worldY = (mouseY - state.position.y) / oldScale;
|
|
78
|
+
state.scale = newScale;
|
|
79
|
+
state.position.x = mouseX - worldX * newScale;
|
|
80
|
+
state.position.y = mouseY - worldY * newScale;
|
|
81
|
+
render();
|
|
82
|
+
},
|
|
83
|
+
{ passive: true }
|
|
84
|
+
);
|
|
85
|
+
window.addEventListener("resize", () => {
|
|
86
|
+
canvas.width = window.innerWidth;
|
|
87
|
+
canvas.height = window.innerHeight;
|
|
88
|
+
render();
|
|
89
|
+
});
|
|
90
|
+
const curveVertex = glsl`#version 300 es
|
|
91
|
+
// Attributes (per-vertex data)
|
|
92
|
+
${interleave("data", [
|
|
93
|
+
attribute.float("a_t"),
|
|
94
|
+
attribute.float("a_side")
|
|
95
|
+
])} // Bezier parameter t ∈ [0,1]
|
|
96
|
+
|
|
97
|
+
// Uniforms (shared data for all vertices)
|
|
98
|
+
${uniform.vec2("u_resolution")} // Canvas size in pixels
|
|
99
|
+
${uniform.vec2("u_offset")} // Pan offset in pixels
|
|
100
|
+
${uniform.float("u_scale")} // Zoom level
|
|
101
|
+
${uniform.vec2("u_p0")} // Control point P0 (start)
|
|
102
|
+
${uniform.vec2("u_p1")} // Control point P1 (control)
|
|
103
|
+
${uniform.vec2("u_p2")} // Control point P2 (end)
|
|
104
|
+
${uniform.float("u_thickness")} // Curve thickness in world units
|
|
105
|
+
|
|
106
|
+
void main() {
|
|
107
|
+
// STEP 1: Evaluate the quadratic bezier curve at parameter t
|
|
108
|
+
// Quadratic bezier formula: B(t) = (1-t)²P0 + 2t(1-t)P1 + t²P2
|
|
109
|
+
float t = a_t;
|
|
110
|
+
float s = 1.0 - t; // s is often used for (1-t) for brevity
|
|
111
|
+
|
|
112
|
+
// Calculate the position on the curve
|
|
113
|
+
vec2 position = s * s * u_p0 + // (1-t)² * P0
|
|
114
|
+
2.0 * s * t * u_p1 + // 2(1-t)t * P1
|
|
115
|
+
t * t * u_p2; // t² * P2
|
|
116
|
+
|
|
117
|
+
// STEP 2: Calculate the tangent vector at this point
|
|
118
|
+
// The derivative of the bezier curve gives us the tangent
|
|
119
|
+
// B'(t) = 2(1-t)(P1-P0) + 2t(P2-P1)
|
|
120
|
+
vec2 tangent = 2.0 * s * (u_p1 - u_p0) + 2.0 * t * (u_p2 - u_p1);
|
|
121
|
+
|
|
122
|
+
// STEP 3: Create a normal vector (perpendicular to tangent)
|
|
123
|
+
// Rotate tangent 90 degrees: (x,y) → (-y,x)
|
|
124
|
+
vec2 normal = normalize(vec2(-tangent.y, tangent.x));
|
|
125
|
+
|
|
126
|
+
// STEP 4: Offset the curve position to create thickness
|
|
127
|
+
// Move the vertex perpendicular to the curve by thickness * side
|
|
128
|
+
vec2 offsetPosition = position + normal * u_thickness * a_side;
|
|
129
|
+
|
|
130
|
+
// STEP 5: Transform from world space to screen space
|
|
131
|
+
vec2 screenPos = offsetPosition * u_scale + u_offset;
|
|
132
|
+
|
|
133
|
+
// STEP 6: Convert to clip space (OpenGL's coordinate system)
|
|
134
|
+
// Screen space: (0,0) is top-left, (width,height) is bottom-right
|
|
135
|
+
// Clip space: (-1,-1) is bottom-left, (1,1) is top-right
|
|
136
|
+
vec2 clipPos = (screenPos / u_resolution) * 2.0 - 1.0;
|
|
137
|
+
clipPos.y = -clipPos.y; // Flip Y to match screen coordinates
|
|
138
|
+
|
|
139
|
+
gl_Position = vec4(clipPos, 0, 1);
|
|
140
|
+
}`;
|
|
141
|
+
const curveFragment = glsl`#version 300 es
|
|
142
|
+
precision mediump float;
|
|
143
|
+
out vec4 fragColor;
|
|
144
|
+
|
|
145
|
+
void main() {
|
|
146
|
+
// Output a solid blue color for the curve
|
|
147
|
+
vec3 curveColor = vec3(0.2, 0.6, 1.0);
|
|
148
|
+
fragColor = vec4(curveColor, 1.0);
|
|
149
|
+
}`;
|
|
150
|
+
const pointVertex = glsl`#version 300 es
|
|
151
|
+
${attribute.vec2("a_vertex")} // Quad vertex position (-1 to 1)
|
|
152
|
+
${uniform.vec2("u_resolution")} // Canvas size
|
|
153
|
+
${uniform.vec2("u_offset")} // Pan offset
|
|
154
|
+
${uniform.float("u_scale")} // Zoom level
|
|
155
|
+
${uniform.vec2("u_pointPos")} // Control point position in world space
|
|
156
|
+
|
|
157
|
+
void main() {
|
|
158
|
+
// Create a small square around the control point position
|
|
159
|
+
vec2 screenPos = (u_pointPos * u_scale + u_offset) + a_vertex * 8.0;
|
|
160
|
+
|
|
161
|
+
// Transform to clip space
|
|
162
|
+
vec2 clipPos = (screenPos / u_resolution) * 2.0 - 1.0;
|
|
163
|
+
clipPos.y = -clipPos.y;
|
|
164
|
+
gl_Position = vec4(clipPos, 0, 1);
|
|
165
|
+
}`;
|
|
166
|
+
const pointFragment = glsl`#version 300 es
|
|
167
|
+
precision mediump float;
|
|
168
|
+
out vec4 fragColor;
|
|
169
|
+
|
|
170
|
+
void main() {
|
|
171
|
+
// Red color for control points
|
|
172
|
+
fragColor = vec4(1.0, 0.3, 0.3, 1.0);
|
|
173
|
+
}`;
|
|
174
|
+
const lineVertex = glsl`#version 300 es
|
|
175
|
+
${attribute.vec2("a_position")} // Line vertex position
|
|
176
|
+
${uniform.vec2("u_resolution")} // Canvas size
|
|
177
|
+
${uniform.vec2("u_offset")} // Pan offset
|
|
178
|
+
${uniform.float("u_scale")} // Zoom level
|
|
179
|
+
|
|
180
|
+
void main() {
|
|
181
|
+
// Transform world position to screen space then clip space
|
|
182
|
+
vec2 screenPos = a_position * u_scale + u_offset;
|
|
183
|
+
vec2 clipPos = (screenPos / u_resolution) * 2.0 - 1.0;
|
|
184
|
+
clipPos.y = -clipPos.y;
|
|
185
|
+
gl_Position = vec4(clipPos, 0, 1);
|
|
186
|
+
}`;
|
|
187
|
+
const lineFragment = glsl`#version 300 es
|
|
188
|
+
precision mediump float;
|
|
189
|
+
out vec4 fragColor;
|
|
190
|
+
|
|
191
|
+
void main() {
|
|
192
|
+
// Semi-transparent gray for control polygon
|
|
193
|
+
fragColor = vec4(0.5, 0.5, 0.5, 0.5);
|
|
194
|
+
}`;
|
|
195
|
+
const { program: curveProgram, schema: curveSchema } = compile(gl, curveVertex, curveFragment);
|
|
196
|
+
const curveView = view(gl, curveProgram, curveSchema);
|
|
197
|
+
const { program: pointProgram, schema: pointSchema } = compile(gl, pointVertex, pointFragment);
|
|
198
|
+
const pointView = view(gl, pointProgram, pointSchema);
|
|
199
|
+
const { program: lineProgram, schema: lineSchema } = compile(gl, lineVertex, lineFragment);
|
|
200
|
+
const lineView = view(gl, lineProgram, lineSchema);
|
|
201
|
+
const curveSegments = 100;
|
|
202
|
+
function generateTriangleStrip() {
|
|
203
|
+
return new Float32Array(
|
|
204
|
+
function* () {
|
|
205
|
+
for (let i = 0; i <= curveSegments; i++) {
|
|
206
|
+
const t = i / curveSegments;
|
|
207
|
+
yield t;
|
|
208
|
+
yield -1;
|
|
209
|
+
yield t;
|
|
210
|
+
yield 1;
|
|
211
|
+
}
|
|
212
|
+
}()
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const stripData = generateTriangleStrip();
|
|
216
|
+
curveView.interleavedAttributes.data.set(stripData);
|
|
217
|
+
const pointVertices = new Float32Array([
|
|
218
|
+
-1,
|
|
219
|
+
-1,
|
|
220
|
+
// Bottom-left
|
|
221
|
+
1,
|
|
222
|
+
-1,
|
|
223
|
+
// Bottom-right
|
|
224
|
+
-1,
|
|
225
|
+
1,
|
|
226
|
+
// Top-left
|
|
227
|
+
-1,
|
|
228
|
+
1,
|
|
229
|
+
// Top-left (repeated for second triangle)
|
|
230
|
+
1,
|
|
231
|
+
-1,
|
|
232
|
+
// Bottom-right (repeated for second triangle)
|
|
233
|
+
1,
|
|
234
|
+
1
|
|
235
|
+
// Top-right
|
|
236
|
+
]);
|
|
237
|
+
pointView.attributes.a_vertex.set(pointVertices);
|
|
238
|
+
function updateLineData() {
|
|
239
|
+
const lineVerticesData = new Float32Array([
|
|
240
|
+
state.controlPoints[0].x,
|
|
241
|
+
state.controlPoints[0].y,
|
|
242
|
+
// P0
|
|
243
|
+
state.controlPoints[1].x,
|
|
244
|
+
state.controlPoints[1].y,
|
|
245
|
+
// P1
|
|
246
|
+
state.controlPoints[1].x,
|
|
247
|
+
state.controlPoints[1].y,
|
|
248
|
+
// P1 (repeated)
|
|
249
|
+
state.controlPoints[2].x,
|
|
250
|
+
state.controlPoints[2].y
|
|
251
|
+
// P2
|
|
252
|
+
]);
|
|
253
|
+
lineView.attributes.a_position.set(lineVerticesData);
|
|
254
|
+
}
|
|
255
|
+
updateLineData();
|
|
256
|
+
function render() {
|
|
257
|
+
if (canvas.width !== window.innerWidth || canvas.height !== window.innerHeight) {
|
|
258
|
+
canvas.width = window.innerWidth;
|
|
259
|
+
canvas.height = window.innerHeight;
|
|
260
|
+
}
|
|
261
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
262
|
+
gl.clearColor(0.1, 0.1, 0.15, 1);
|
|
263
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
264
|
+
gl.enable(gl.BLEND);
|
|
265
|
+
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
|
266
|
+
updateLineData();
|
|
267
|
+
gl.useProgram(lineProgram);
|
|
268
|
+
lineView.attributes.a_position.bind();
|
|
269
|
+
lineView.uniforms.u_resolution.set(canvas.width, canvas.height);
|
|
270
|
+
lineView.uniforms.u_offset.set(state.position.x, state.position.y);
|
|
271
|
+
lineView.uniforms.u_scale.set(state.scale);
|
|
272
|
+
gl.drawArrays(gl.LINES, 0, 4);
|
|
273
|
+
gl.useProgram(curveProgram);
|
|
274
|
+
curveView.interleavedAttributes.data.bind();
|
|
275
|
+
curveView.uniforms.u_resolution.set(canvas.width, canvas.height);
|
|
276
|
+
curveView.uniforms.u_offset.set(state.position.x, state.position.y);
|
|
277
|
+
curveView.uniforms.u_scale.set(state.scale);
|
|
278
|
+
curveView.uniforms.u_p0.set(state.controlPoints[0].x, state.controlPoints[0].y);
|
|
279
|
+
curveView.uniforms.u_p1.set(state.controlPoints[1].x, state.controlPoints[1].y);
|
|
280
|
+
curveView.uniforms.u_p2.set(state.controlPoints[2].x, state.controlPoints[2].y);
|
|
281
|
+
curveView.uniforms.u_thickness.set(5);
|
|
282
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, (curveSegments + 1) * 2);
|
|
283
|
+
curveView.interleavedAttributes.data.unbind();
|
|
284
|
+
gl.useProgram(pointProgram);
|
|
285
|
+
pointView.attributes.a_vertex.bind();
|
|
286
|
+
pointView.uniforms.u_resolution.set(canvas.width, canvas.height);
|
|
287
|
+
pointView.uniforms.u_offset.set(state.position.x, state.position.y);
|
|
288
|
+
pointView.uniforms.u_scale.set(state.scale);
|
|
289
|
+
for (const point of state.controlPoints) {
|
|
290
|
+
pointView.uniforms.u_pointPos.set(point.x, point.y);
|
|
291
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
render();
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { b as createFramebuffer, a as attribute, g as glsl, u as uniform, c as compile, v as view } from './tag-C_bg62fD.js';
|
|
2
|
+
|
|
3
|
+
let playing = false;
|
|
4
|
+
const canvas = document.createElement("canvas");
|
|
5
|
+
const gl = canvas.getContext("webgl", { antialias: false });
|
|
6
|
+
if (!gl) {
|
|
7
|
+
throw new Error("WebGL not supported");
|
|
8
|
+
}
|
|
9
|
+
document.body.append(canvas);
|
|
10
|
+
function spray() {
|
|
11
|
+
return Math.floor(Math.random() * 8) * (Math.random() < 0.5 ? 1 : -1);
|
|
12
|
+
}
|
|
13
|
+
canvas.addEventListener("mousedown", (event) => {
|
|
14
|
+
const rect = canvas.getBoundingClientRect();
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
playing = false;
|
|
17
|
+
window.addEventListener(
|
|
18
|
+
"mousemove",
|
|
19
|
+
(event2) => {
|
|
20
|
+
const x = Math.floor((event2.clientX - rect.left - spray()) * (canvas.width / rect.width));
|
|
21
|
+
const y = Math.floor((rect.bottom - event2.clientY - spray()) * (canvas.height / rect.height));
|
|
22
|
+
const dimensions = [2, 2];
|
|
23
|
+
const data = new Uint8Array(
|
|
24
|
+
function* () {
|
|
25
|
+
for (let i = 0; i < dimensions[0] * dimensions[1]; i++) {
|
|
26
|
+
yield 255;
|
|
27
|
+
yield 0;
|
|
28
|
+
yield 0;
|
|
29
|
+
yield 255;
|
|
30
|
+
}
|
|
31
|
+
}()
|
|
32
|
+
);
|
|
33
|
+
gl.bindTexture(gl.TEXTURE_2D, read.texture);
|
|
34
|
+
gl.texSubImage2D(
|
|
35
|
+
gl.TEXTURE_2D,
|
|
36
|
+
0,
|
|
37
|
+
x,
|
|
38
|
+
y,
|
|
39
|
+
dimensions[0],
|
|
40
|
+
dimensions[1],
|
|
41
|
+
gl.RGBA,
|
|
42
|
+
gl.UNSIGNED_BYTE,
|
|
43
|
+
data
|
|
44
|
+
);
|
|
45
|
+
gl.bindTexture(gl.TEXTURE_2D, write.texture);
|
|
46
|
+
gl.texSubImage2D(
|
|
47
|
+
gl.TEXTURE_2D,
|
|
48
|
+
0,
|
|
49
|
+
x,
|
|
50
|
+
y,
|
|
51
|
+
dimensions[0],
|
|
52
|
+
dimensions[1],
|
|
53
|
+
gl.RGBA,
|
|
54
|
+
gl.UNSIGNED_BYTE,
|
|
55
|
+
data
|
|
56
|
+
);
|
|
57
|
+
render();
|
|
58
|
+
},
|
|
59
|
+
controller
|
|
60
|
+
);
|
|
61
|
+
window.addEventListener(
|
|
62
|
+
"mouseup",
|
|
63
|
+
() => {
|
|
64
|
+
controller.abort();
|
|
65
|
+
playing = true;
|
|
66
|
+
animate();
|
|
67
|
+
},
|
|
68
|
+
controller
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
canvas.width = 256 * 2;
|
|
72
|
+
canvas.height = 256 * 2;
|
|
73
|
+
const WIDTH = canvas.width;
|
|
74
|
+
const HEIGHT = canvas.height;
|
|
75
|
+
const vertex = glsl`
|
|
76
|
+
${attribute.vec2("a_vertex")}
|
|
77
|
+
varying vec2 v_uv;
|
|
78
|
+
void main() {
|
|
79
|
+
v_uv = a_vertex * 0.5 + 0.5;
|
|
80
|
+
gl_Position = vec4(a_vertex, 0, 1);
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
const stepFragment = glsl`
|
|
84
|
+
precision mediump float;
|
|
85
|
+
varying vec2 v_uv;
|
|
86
|
+
${uniform.sampler2D("u_texture")}
|
|
87
|
+
${uniform.vec2("u_texelSize")}
|
|
88
|
+
|
|
89
|
+
int getCell(vec2 uv) {
|
|
90
|
+
// Wrap around (toroidal)
|
|
91
|
+
uv = mod(uv, 1.0);
|
|
92
|
+
float val = texture2D(u_texture, uv).r;
|
|
93
|
+
return int(step(0.5, val));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void main() {
|
|
97
|
+
int sum = 0;
|
|
98
|
+
// Iterate neighbors -1,0,1
|
|
99
|
+
for (int y = -1; y <= 1; y++) {
|
|
100
|
+
for (int x = -1; x <= 1; x++) {
|
|
101
|
+
if (x == 0 && y == 0) continue;
|
|
102
|
+
sum += getCell(v_uv + vec2(float(x), float(y)) * u_texelSize);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
int current = getCell(v_uv);
|
|
106
|
+
|
|
107
|
+
int nextState = 0;
|
|
108
|
+
if (current == 1) {
|
|
109
|
+
if (sum == 2 || sum == 3) nextState = 1;
|
|
110
|
+
else nextState = 0;
|
|
111
|
+
} else {
|
|
112
|
+
if (sum == 3) nextState = 1;
|
|
113
|
+
else nextState = 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
gl_FragColor = vec4(float(nextState), 0.0, 0.0, 1.0);
|
|
117
|
+
}`;
|
|
118
|
+
const renderFragment = glsl`
|
|
119
|
+
precision mediump float;
|
|
120
|
+
varying vec2 v_uv;
|
|
121
|
+
${uniform.sampler2D("u_texture")}
|
|
122
|
+
|
|
123
|
+
void main() {
|
|
124
|
+
float cell = texture2D(u_texture, v_uv).r;
|
|
125
|
+
gl_FragColor = vec4(vec3(cell), 1.0);
|
|
126
|
+
}`;
|
|
127
|
+
const { program: stepProgram, schema: stepSchema } = compile(gl, vertex, stepFragment);
|
|
128
|
+
const stepView = view(gl, stepProgram, stepSchema);
|
|
129
|
+
const { program: renderProgram, schema: renderSchema } = compile(gl, vertex, renderFragment);
|
|
130
|
+
const renderView = view(gl, renderProgram, {
|
|
131
|
+
...renderSchema,
|
|
132
|
+
attributes: {
|
|
133
|
+
...renderSchema.attributes,
|
|
134
|
+
a_vertex: {
|
|
135
|
+
...renderSchema.attributes.a_vertex,
|
|
136
|
+
buffer: stepView.attributes.a_vertex.buffer
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
stepView.attributes.a_vertex.set(new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]));
|
|
141
|
+
const framebufferOptions = {
|
|
142
|
+
attachment: "color",
|
|
143
|
+
width: WIDTH,
|
|
144
|
+
height: HEIGHT,
|
|
145
|
+
type: "UNSIGNED_BYTE",
|
|
146
|
+
magFilter: "NEAREST",
|
|
147
|
+
minFilter: "NEAREST"
|
|
148
|
+
};
|
|
149
|
+
let read = createFramebuffer(gl, framebufferOptions);
|
|
150
|
+
let write = createFramebuffer(gl, framebufferOptions);
|
|
151
|
+
gl.bindTexture(gl.TEXTURE_2D, read.texture);
|
|
152
|
+
gl.texSubImage2D(
|
|
153
|
+
gl.TEXTURE_2D,
|
|
154
|
+
0,
|
|
155
|
+
0,
|
|
156
|
+
0,
|
|
157
|
+
WIDTH,
|
|
158
|
+
HEIGHT,
|
|
159
|
+
gl.RGBA,
|
|
160
|
+
gl.UNSIGNED_BYTE,
|
|
161
|
+
new Uint8Array(
|
|
162
|
+
function* () {
|
|
163
|
+
for (let i = 0; i < WIDTH * HEIGHT; i++) {
|
|
164
|
+
yield 0;
|
|
165
|
+
yield 0;
|
|
166
|
+
yield 0;
|
|
167
|
+
yield 255;
|
|
168
|
+
}
|
|
169
|
+
}()
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
function step() {
|
|
173
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, write.framebuffer);
|
|
174
|
+
gl.viewport(0, 0, WIDTH, HEIGHT);
|
|
175
|
+
gl.useProgram(stepProgram);
|
|
176
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
177
|
+
gl.bindTexture(gl.TEXTURE_2D, read.texture);
|
|
178
|
+
stepView.uniforms.u_texture.set(0);
|
|
179
|
+
stepView.uniforms.u_texelSize.set(1 / WIDTH, 1 / HEIGHT);
|
|
180
|
+
stepView.attributes.a_vertex.bind();
|
|
181
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
182
|
+
}
|
|
183
|
+
function render() {
|
|
184
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
185
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
186
|
+
gl.useProgram(renderProgram);
|
|
187
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
188
|
+
gl.bindTexture(gl.TEXTURE_2D, write.texture);
|
|
189
|
+
renderView.uniforms.u_texture.set(0);
|
|
190
|
+
renderView.attributes.a_vertex.bind();
|
|
191
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
192
|
+
}
|
|
193
|
+
function animate() {
|
|
194
|
+
if (playing) {
|
|
195
|
+
step();
|
|
196
|
+
render();
|
|
197
|
+
[read, write] = [write, read];
|
|
198
|
+
requestAnimationFrame(animate);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
playing = true;
|
|
202
|
+
animate();
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { a as attribute, u as uniform, g as glsl, c as compile, v as view } from './tag-C_bg62fD.js';
|
|
2
|
+
import { c as createElement, a as cursor } from './utils-CvCgsM08.js';
|
|
3
|
+
|
|
4
|
+
const canvas = createElement("canvas", { width: window.innerWidth, height: window.innerHeight });
|
|
5
|
+
const gl = canvas.getContext("webgl2", { antialias: false });
|
|
6
|
+
if (!gl) {
|
|
7
|
+
throw new Error("WebGL not supported");
|
|
8
|
+
}
|
|
9
|
+
const state = {
|
|
10
|
+
position: { x: 0, y: 0 },
|
|
11
|
+
scale: 1,
|
|
12
|
+
elements: []
|
|
13
|
+
};
|
|
14
|
+
const container = createElement("div");
|
|
15
|
+
state.elements.push(createGridElement({ x: 100, y: 100 }));
|
|
16
|
+
function createGridElement(position = { x: 0, y: 0 }) {
|
|
17
|
+
position = {
|
|
18
|
+
x: (position.x - state.position.x) / state.scale,
|
|
19
|
+
y: (position.y - state.position.y) / state.scale
|
|
20
|
+
};
|
|
21
|
+
const element = createElement("div", {
|
|
22
|
+
innerText: "HALLO WORLD",
|
|
23
|
+
style: `position: fixed;
|
|
24
|
+
z-index: 10;
|
|
25
|
+
left: 0px;
|
|
26
|
+
top: 0px;
|
|
27
|
+
transform-origin: top left;
|
|
28
|
+
background: white;
|
|
29
|
+
padding: 2px;
|
|
30
|
+
border-radius: 2px;`,
|
|
31
|
+
parentElement: container
|
|
32
|
+
});
|
|
33
|
+
function update() {
|
|
34
|
+
element.style.transform = `translate3d(${state.position.x + position.x * state.scale}px,${state.position.y + position.y * state.scale}px,0) scale(${state.scale})`;
|
|
35
|
+
}
|
|
36
|
+
update();
|
|
37
|
+
return {
|
|
38
|
+
element,
|
|
39
|
+
position,
|
|
40
|
+
update
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
canvas.addEventListener("pointerdown", (event) => {
|
|
44
|
+
cursor(event, (event2) => {
|
|
45
|
+
state.position.x -= event2.deltaX;
|
|
46
|
+
state.position.y -= event2.deltaY;
|
|
47
|
+
render();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
canvas.addEventListener("dblclick", (event) => {
|
|
51
|
+
state.elements.push(
|
|
52
|
+
createGridElement({
|
|
53
|
+
x: event.clientX,
|
|
54
|
+
y: event.clientY
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
window.addEventListener(
|
|
59
|
+
"wheel",
|
|
60
|
+
(event) => {
|
|
61
|
+
const rect = canvas.getBoundingClientRect();
|
|
62
|
+
const mouseX = event.clientX - rect.left;
|
|
63
|
+
const mouseY = event.clientY - rect.top;
|
|
64
|
+
const scaleFactor = event.deltaY > 0 ? 0.95 : 1.05;
|
|
65
|
+
const oldScale = state.scale;
|
|
66
|
+
const newScale = Math.max(0.1, Math.min(10, oldScale * scaleFactor));
|
|
67
|
+
const gridX = (mouseX - state.position.x) / oldScale;
|
|
68
|
+
const gridY = (mouseY - state.position.y) / oldScale;
|
|
69
|
+
state.scale = newScale;
|
|
70
|
+
state.position.x = mouseX - gridX * newScale;
|
|
71
|
+
state.position.y = mouseY - gridY * newScale;
|
|
72
|
+
render();
|
|
73
|
+
},
|
|
74
|
+
{ passive: true }
|
|
75
|
+
);
|
|
76
|
+
window.addEventListener("resize", render);
|
|
77
|
+
const vertex = glsl`#version 300 es
|
|
78
|
+
${attribute.vec2("a_vertex")}
|
|
79
|
+
${uniform.vec2("u_screenSize")}
|
|
80
|
+
${uniform.vec2("u_pos")}
|
|
81
|
+
${uniform.float("u_scale")}
|
|
82
|
+
out vec2 v_gridCoord;
|
|
83
|
+
|
|
84
|
+
// Direct mapping: (-1,-1) is bottom-left, (1,1) is top-right in clip space
|
|
85
|
+
// We want (0,0) to be top-left in pixel space
|
|
86
|
+
vec2 project(vec2 pos, vec2 size){
|
|
87
|
+
return vec2(
|
|
88
|
+
(pos.x + 1.0),
|
|
89
|
+
(1.0 - pos.y)
|
|
90
|
+
) * size * 0.5;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void main() {
|
|
94
|
+
vec2 pixelPos = project(a_vertex, u_screenSize);
|
|
95
|
+
|
|
96
|
+
// Apply offset and scale to get grid coordinates
|
|
97
|
+
v_gridCoord = (pixelPos - u_pos) / u_scale;
|
|
98
|
+
|
|
99
|
+
gl_Position = vec4(a_vertex, 0, 1);
|
|
100
|
+
}`;
|
|
101
|
+
const renderFragment = glsl`#version 300 es
|
|
102
|
+
precision highp float;
|
|
103
|
+
in vec2 v_gridCoord;
|
|
104
|
+
out vec4 fragColor;
|
|
105
|
+
${uniform.float("u_thickness")}
|
|
106
|
+
|
|
107
|
+
float grid(vec2 coord, float thickness) {
|
|
108
|
+
vec2 cell = mod(coord, 100.0);
|
|
109
|
+
vec2 derivative = fwidth(coord);
|
|
110
|
+
|
|
111
|
+
// Convert thickness from screen pixels to grid space
|
|
112
|
+
vec2 screenThickness = derivative * thickness;
|
|
113
|
+
|
|
114
|
+
// Ensure minimum thickness to prevent lines from disappearing
|
|
115
|
+
vec2 minThickness = max(screenThickness, derivative * 0.5);
|
|
116
|
+
|
|
117
|
+
// Create hard edges by checking if we're within thickness of a grid line
|
|
118
|
+
float lineX = step(cell.x, minThickness.x) + step(100.0 - minThickness.x, cell.x);
|
|
119
|
+
float lineY = step(cell.y, minThickness.y) + step(100.0 - minThickness.y, cell.y);
|
|
120
|
+
|
|
121
|
+
// Clamp to 0-1 range and return maximum (union of lines)
|
|
122
|
+
return max(min(lineX, 1.0), min(lineY, 1.0));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void main() {
|
|
126
|
+
// Create grid pattern with adjustable thickness
|
|
127
|
+
float gridPattern = grid(v_gridCoord, u_thickness);
|
|
128
|
+
|
|
129
|
+
// Grid colors
|
|
130
|
+
vec3 backgroundColor = vec3(0.1, 0.1, 0.15);
|
|
131
|
+
vec3 gridColor = vec3(0.3, 0.3, 0.4);
|
|
132
|
+
vec3 color = mix(backgroundColor, gridColor, gridPattern);
|
|
133
|
+
|
|
134
|
+
fragColor = vec4(color, 1.0);
|
|
135
|
+
}`;
|
|
136
|
+
const { program, schema } = compile(gl, vertex, renderFragment);
|
|
137
|
+
const { attributes, uniforms } = view(gl, program, schema);
|
|
138
|
+
attributes.a_vertex.set(new Float32Array([
|
|
139
|
+
-1,
|
|
140
|
+
-1,
|
|
141
|
+
// bottom-left
|
|
142
|
+
1,
|
|
143
|
+
-1,
|
|
144
|
+
// bottom-right
|
|
145
|
+
-1,
|
|
146
|
+
1,
|
|
147
|
+
// top-left
|
|
148
|
+
-1,
|
|
149
|
+
1,
|
|
150
|
+
// top-left
|
|
151
|
+
1,
|
|
152
|
+
-1,
|
|
153
|
+
// bottom-right
|
|
154
|
+
1,
|
|
155
|
+
1
|
|
156
|
+
// top-right
|
|
157
|
+
]));
|
|
158
|
+
function render() {
|
|
159
|
+
gl.useProgram(program);
|
|
160
|
+
canvas.width = window.innerWidth;
|
|
161
|
+
canvas.height = window.innerHeight;
|
|
162
|
+
gl.viewport(0, 0, canvas.width, canvas.height);
|
|
163
|
+
gl.clearColor(0, 0, 0, 1);
|
|
164
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
165
|
+
attributes.a_vertex.bind();
|
|
166
|
+
uniforms.u_screenSize.set(canvas.width, canvas.height);
|
|
167
|
+
uniforms.u_pos.set(state.position.x, state.position.y);
|
|
168
|
+
uniforms.u_scale.set(state.scale);
|
|
169
|
+
uniforms.u_thickness.set(1);
|
|
170
|
+
state.elements.forEach((element) => element.update());
|
|
171
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
172
|
+
}
|
|
173
|
+
render();
|