@bigmistqke/view.gl 0.2.0 → 0.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/README.md +82 -27
- package/dev/dist/404.html +19 -0
- package/dev/dist/assets/bezier-DrA4cU_H.js +292 -0
- package/dev/dist/assets/game-of-life-BYlzWhEL.js +186 -0
- package/dev/dist/assets/glsl-composition-D_BaFFij.js +97 -0
- package/dev/dist/assets/grid-DfXHNLBm.js +181 -0
- package/dev/dist/assets/index-C2N3u8wj.css +12 -0
- package/dev/dist/assets/index-D-N0FDYs.js +110 -0
- package/dev/dist/assets/main-CSE2fki3.js +32 -0
- package/dev/dist/assets/overview-IeUhUflE.js +80 -0
- package/dev/dist/assets/pix-sim-DMJJA_WO.js +500 -0
- package/dev/dist/assets/ray-casting-DTQetDPd.js +411 -0
- package/dev/dist/assets/ray-casting-modules-Dkfw4tdc.js +765 -0
- package/dev/dist/assets/sdf-CS0uBonx.js +66 -0
- package/dev/dist/assets/symbol-BL_7GJ0M.js +67 -0
- package/dev/dist/assets/tag-C0Vskpp8.js +833 -0
- package/dev/dist/assets/tetris-D8mokodr.js +344 -0
- package/dev/dist/assets/utils-2dzuv_bW.js +62 -0
- package/dev/dist/index.html +51 -0
- package/dist/chunk-BZDGEIOT.mjs +278 -0
- package/dist/chunk-BZDGEIOT.mjs.map +1 -0
- package/dist/{chunk-EYXOSM7B.mjs → chunk-X4M3LV4R.mjs} +60 -133
- package/dist/chunk-X4M3LV4R.mjs.map +1 -0
- package/dist/gl.d.mts +2 -2
- package/dist/gl.mjs +1 -1
- package/dist/index.d.mts +13 -4
- package/dist/index.mjs +7 -2
- package/dist/tag.d.mts +6 -1
- package/dist/tag.mjs +13 -5
- package/dist/tag.mjs.map +1 -1
- package/dist/{types-CyGyGi18.d.mts → types-CNdqh7tB.d.mts} +34 -4
- package/package.json +1 -1
- package/src/gl.ts +13 -2
- package/src/index.ts +55 -39
- package/src/tag.ts +18 -2
- package/src/types.ts +33 -3
- package/src/utils.ts +45 -10
- package/test/index.test.ts +111 -29
- package/test/mocks/webgl.ts +14 -0
- package/test/tag.test.ts +41 -0
- package/dist/chunk-CJTT7WPV.mjs +0 -75
- package/dist/chunk-CJTT7WPV.mjs.map +0 -1
- package/dist/chunk-EYXOSM7B.mjs.map +0 -1
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { b as createFramebuffer, a as attribute, g as glsl, u as uniform, c as compile } from './tag-C0Vskpp8.js';
|
|
2
|
+
import { d as dom } from './utils-2dzuv_bW.js';
|
|
3
|
+
|
|
4
|
+
const MATERIALS = {
|
|
5
|
+
SAND: {
|
|
6
|
+
color: [1, 0.8, 0],
|
|
7
|
+
// Yellow sand
|
|
8
|
+
id: 1
|
|
9
|
+
},
|
|
10
|
+
WATER: {
|
|
11
|
+
color: [0, 0.5, 1],
|
|
12
|
+
// Blue water
|
|
13
|
+
id: 2
|
|
14
|
+
},
|
|
15
|
+
STONE: {
|
|
16
|
+
color: [0.5, 0.5, 0.5],
|
|
17
|
+
// Gray stone
|
|
18
|
+
id: 3
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const MATERIAL_SIZE = Object.keys(MATERIALS).length;
|
|
22
|
+
let playing = false;
|
|
23
|
+
let currentMaterial = MATERIALS.SAND.id;
|
|
24
|
+
const container = dom("div", {
|
|
25
|
+
style: {
|
|
26
|
+
display: "flex",
|
|
27
|
+
flexDirection: "column",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
gap: "10px"
|
|
30
|
+
},
|
|
31
|
+
parentElement: document.body
|
|
32
|
+
});
|
|
33
|
+
const buttonContainer = dom("div", {
|
|
34
|
+
style: "display: flex; gap: 10px; margin-bottom: 10px;",
|
|
35
|
+
parentElement: container
|
|
36
|
+
});
|
|
37
|
+
Object.entries(MATERIALS).forEach(([name, material]) => {
|
|
38
|
+
dom("button", {
|
|
39
|
+
"data-name": name,
|
|
40
|
+
"data-id": material.id.toString(),
|
|
41
|
+
textContent: name.toLowerCase(),
|
|
42
|
+
style: {
|
|
43
|
+
padding: "5px 10px",
|
|
44
|
+
fontSize: "14px",
|
|
45
|
+
fontFamily: "arial",
|
|
46
|
+
cursor: "pointer",
|
|
47
|
+
color: "black",
|
|
48
|
+
background: currentMaterial === material.id ? `rgb(${material.color.map((c) => Math.floor(c * 255)).join(",")})` : `rgba(${material.color.map((c) => Math.floor(c * 255)).join(",")}, 0.75)`,
|
|
49
|
+
borderColor: name === "SAND" ? "black" : "white",
|
|
50
|
+
border: `2px solid rgb(${material.color.map((c) => Math.floor(c * 255)).join(",")})`,
|
|
51
|
+
borderRadius: "2px"
|
|
52
|
+
},
|
|
53
|
+
onclick() {
|
|
54
|
+
currentMaterial = material.id;
|
|
55
|
+
buttonContainer.querySelectorAll("button").forEach((btn) => {
|
|
56
|
+
const name2 = btn.getAttribute("data-name");
|
|
57
|
+
const id = btn.getAttribute("data-id");
|
|
58
|
+
if (id === currentMaterial.toString()) {
|
|
59
|
+
btn.style.background = `rgb(${MATERIALS[name2].color.map((c) => Math.floor(c * 255)).join(",")})`;
|
|
60
|
+
} else {
|
|
61
|
+
btn.style.background = `rgba(${MATERIALS[name2].color.map((c) => Math.floor(c * 255)).join(",")}, 0.75)`;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
parentElement: buttonContainer
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
function spray() {
|
|
69
|
+
return Math.floor(Math.random() * 8) * (Math.random() < 0.5 ? 1 : -1);
|
|
70
|
+
}
|
|
71
|
+
const canvas = dom("canvas", {
|
|
72
|
+
parentElement: container,
|
|
73
|
+
onmousedown() {
|
|
74
|
+
const rect = canvas.getBoundingClientRect();
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
window.addEventListener(
|
|
77
|
+
"mousemove",
|
|
78
|
+
(event) => {
|
|
79
|
+
const x = Math.floor((event.clientX - rect.left + spray()) * (canvas.width / rect.width));
|
|
80
|
+
const y = Math.floor(
|
|
81
|
+
(rect.bottom - event.clientY + spray()) * (canvas.height / rect.height)
|
|
82
|
+
);
|
|
83
|
+
const dimensions = [8, 8];
|
|
84
|
+
const data = new Uint8Array(
|
|
85
|
+
function* () {
|
|
86
|
+
for (let i = 0; i < dimensions[0] * dimensions[1]; i++) {
|
|
87
|
+
yield (currentMaterial & 1) * 255;
|
|
88
|
+
yield (currentMaterial >> 1 & 1) * 255;
|
|
89
|
+
yield (currentMaterial >> 2 & 1) * 255;
|
|
90
|
+
yield 255;
|
|
91
|
+
}
|
|
92
|
+
}()
|
|
93
|
+
);
|
|
94
|
+
gl.bindTexture(gl.TEXTURE_2D, read.texture);
|
|
95
|
+
gl.texSubImage2D(
|
|
96
|
+
gl.TEXTURE_2D,
|
|
97
|
+
0,
|
|
98
|
+
Math.max(0, Math.min(WIDTH - dimensions[0], x)),
|
|
99
|
+
Math.max(0, Math.min(HEIGHT - dimensions[1], y)),
|
|
100
|
+
dimensions[0],
|
|
101
|
+
dimensions[1],
|
|
102
|
+
gl.RGBA,
|
|
103
|
+
gl.UNSIGNED_BYTE,
|
|
104
|
+
data
|
|
105
|
+
);
|
|
106
|
+
gl.bindTexture(gl.TEXTURE_2D, write.texture);
|
|
107
|
+
gl.texSubImage2D(
|
|
108
|
+
gl.TEXTURE_2D,
|
|
109
|
+
0,
|
|
110
|
+
Math.max(0, Math.min(WIDTH - dimensions[0], x)),
|
|
111
|
+
Math.max(0, Math.min(HEIGHT - dimensions[1], y)),
|
|
112
|
+
dimensions[0],
|
|
113
|
+
dimensions[1],
|
|
114
|
+
gl.RGBA,
|
|
115
|
+
gl.UNSIGNED_BYTE,
|
|
116
|
+
data
|
|
117
|
+
);
|
|
118
|
+
render();
|
|
119
|
+
},
|
|
120
|
+
controller
|
|
121
|
+
);
|
|
122
|
+
window.addEventListener(
|
|
123
|
+
"mouseup",
|
|
124
|
+
() => {
|
|
125
|
+
controller.abort();
|
|
126
|
+
},
|
|
127
|
+
controller
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
const gl = canvas.getContext("webgl2", { antialias: false });
|
|
132
|
+
if (!gl) {
|
|
133
|
+
throw new Error("WebGL not supported");
|
|
134
|
+
}
|
|
135
|
+
canvas.width = 256 * 2;
|
|
136
|
+
canvas.height = 256 * 2;
|
|
137
|
+
const WIDTH = canvas.width;
|
|
138
|
+
const HEIGHT = canvas.height;
|
|
139
|
+
const vertex = glsl`#version 300 es
|
|
140
|
+
${attribute.vec2("a_vertex")}
|
|
141
|
+
out vec2 v_uv;
|
|
142
|
+
void main() {
|
|
143
|
+
v_uv = a_vertex * 0.5 + 0.5;
|
|
144
|
+
gl_Position = vec4(a_vertex, 0, 1);
|
|
145
|
+
}`;
|
|
146
|
+
const stepFragment = glsl`#version 300 es
|
|
147
|
+
precision highp float;
|
|
148
|
+
in vec2 v_uv;
|
|
149
|
+
${uniform.sampler2D("u_texture")}
|
|
150
|
+
${uniform.vec2("u_texelSize")}
|
|
151
|
+
out vec4 fragColor;
|
|
152
|
+
|
|
153
|
+
// Material constants (like enums)
|
|
154
|
+
const int MAT_EMPTY = 0;
|
|
155
|
+
const int MAT_SAND = 1;
|
|
156
|
+
const int MAT_WATER = 2;
|
|
157
|
+
const int MAT_STONE = 3;
|
|
158
|
+
|
|
159
|
+
// Direction constants
|
|
160
|
+
const vec2 DIR_UP = vec2(0.0, 1.0);
|
|
161
|
+
const vec2 DIR_DOWN = vec2(0.0, -1.0);
|
|
162
|
+
const vec2 DIR_LEFT = vec2(-1.0, 0.0);
|
|
163
|
+
const vec2 DIR_RIGHT = vec2(1.0, 0.0);
|
|
164
|
+
|
|
165
|
+
// Helper function to decode material from RGB
|
|
166
|
+
int getMaterial(vec4 color) {
|
|
167
|
+
return int(color.r + color.g * 2.0 + color.b * 4.0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Helper function to encode material to RGB
|
|
171
|
+
vec4 encodeMaterial(int material) {
|
|
172
|
+
return vec4(
|
|
173
|
+
float(material & 1),
|
|
174
|
+
float((material >> 1) & 1),
|
|
175
|
+
float((material >> 2) & 1),
|
|
176
|
+
1.0
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
void main() {
|
|
181
|
+
// Sample the current state
|
|
182
|
+
vec4 current = texture(u_texture, v_uv);
|
|
183
|
+
|
|
184
|
+
// Check boundaries
|
|
185
|
+
bool atBottom = v_uv.y - u_texelSize.y < 0.0;
|
|
186
|
+
bool atLeft = v_uv.x - u_texelSize.x < 0.0;
|
|
187
|
+
bool atRight = v_uv.x + u_texelSize.x > 1.0;
|
|
188
|
+
bool atTop = v_uv.y + u_texelSize.y > 1.0;
|
|
189
|
+
|
|
190
|
+
// Default: keep current state
|
|
191
|
+
fragColor = current;
|
|
192
|
+
|
|
193
|
+
int material = getMaterial(current);
|
|
194
|
+
|
|
195
|
+
int below = getMaterial(texture(u_texture, v_uv + DIR_DOWN * u_texelSize));
|
|
196
|
+
int above = getMaterial(texture(u_texture, v_uv + DIR_UP * u_texelSize));
|
|
197
|
+
int aboveLeft = getMaterial(texture(u_texture, v_uv + (DIR_UP + DIR_LEFT) * u_texelSize));
|
|
198
|
+
int aboveRight = getMaterial(texture(u_texture, v_uv + (DIR_UP + DIR_RIGHT) * u_texelSize));
|
|
199
|
+
int belowLeft = getMaterial(texture(u_texture, v_uv + (DIR_DOWN + DIR_LEFT) * u_texelSize));
|
|
200
|
+
int belowRight = getMaterial(texture(u_texture, v_uv + (DIR_DOWN + DIR_RIGHT) * u_texelSize));
|
|
201
|
+
|
|
202
|
+
// Physics simulation using switch
|
|
203
|
+
switch (material) {
|
|
204
|
+
case MAT_EMPTY:{
|
|
205
|
+
// Empty cell - check what can fall/flow into it
|
|
206
|
+
|
|
207
|
+
// Check for sand from above (highest priority - sand is denser)
|
|
208
|
+
bool sandDirectlyAbove = !atTop && above == MAT_SAND;
|
|
209
|
+
if (sandDirectlyAbove) {
|
|
210
|
+
fragColor = encodeMaterial(MAT_SAND);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check for sand sliding from diagonal
|
|
215
|
+
bool sandAboveLeft = !atTop && !atRight && aboveLeft == MAT_SAND;
|
|
216
|
+
bool sandAboveRight = !atTop && !atLeft && aboveRight == MAT_SAND;
|
|
217
|
+
|
|
218
|
+
if (sandAboveLeft) {
|
|
219
|
+
vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
|
|
220
|
+
bool leftBlocked = getMaterial(left) != MAT_EMPTY;
|
|
221
|
+
bool canSlideFromLeft = leftBlocked || atBottom;
|
|
222
|
+
if (canSlideFromLeft) {
|
|
223
|
+
fragColor = encodeMaterial(MAT_SAND);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (sandAboveRight) {
|
|
229
|
+
vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
|
|
230
|
+
bool rightBlocked = getMaterial(right) != MAT_EMPTY;
|
|
231
|
+
bool canSlideFromRight = rightBlocked || atBottom;
|
|
232
|
+
if (canSlideFromRight) {
|
|
233
|
+
fragColor = encodeMaterial(MAT_SAND);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check for water from above
|
|
239
|
+
bool waterDirectlyAbove = !atTop && above == MAT_WATER;
|
|
240
|
+
if (waterDirectlyAbove) {
|
|
241
|
+
fragColor = encodeMaterial(MAT_WATER);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check for water sliding from diagonal
|
|
246
|
+
bool waterAboveLeft = !atTop && !atRight && aboveLeft == MAT_WATER;
|
|
247
|
+
bool waterAboveRight = !atTop && !atLeft && aboveRight == MAT_WATER;
|
|
248
|
+
|
|
249
|
+
if (waterAboveLeft) {
|
|
250
|
+
vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
|
|
251
|
+
bool sandToLeft = getMaterial(left) == MAT_SAND;
|
|
252
|
+
if (!sandToLeft) {
|
|
253
|
+
fragColor = encodeMaterial(MAT_WATER);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (waterAboveRight) {
|
|
259
|
+
vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
|
|
260
|
+
bool sandToRight = getMaterial(right) == MAT_SAND;
|
|
261
|
+
if (!sandToRight) {
|
|
262
|
+
fragColor = encodeMaterial(MAT_WATER);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check for water flowing horizontally
|
|
268
|
+
vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
|
|
269
|
+
vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
|
|
270
|
+
int matLeft = getMaterial(left);
|
|
271
|
+
int matRight = getMaterial(right);
|
|
272
|
+
|
|
273
|
+
bool waterToLeft = !atRight && matLeft == MAT_WATER;
|
|
274
|
+
bool waterToRight = !atLeft && matRight == MAT_WATER;
|
|
275
|
+
|
|
276
|
+
if (!waterToLeft && !waterToRight) break;
|
|
277
|
+
|
|
278
|
+
// Random choice for symmetric flow
|
|
279
|
+
float rand = fract(sin(dot(v_uv * 1000.0, vec2(12.9898, 78.233))) * 43758.5453);
|
|
280
|
+
bool preferLeft = rand < 0.5;
|
|
281
|
+
|
|
282
|
+
if (
|
|
283
|
+
waterToLeft && waterToRight ||
|
|
284
|
+
waterToLeft ||
|
|
285
|
+
waterToRight
|
|
286
|
+
) {
|
|
287
|
+
fragColor = encodeMaterial(MAT_WATER);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
case MAT_SAND: {
|
|
293
|
+
// Sand particle - check if it should fall
|
|
294
|
+
bool canFallThrough = below == MAT_EMPTY || below == MAT_WATER;
|
|
295
|
+
bool canFallDown = !atBottom && canFallThrough;
|
|
296
|
+
|
|
297
|
+
if (canFallDown) {
|
|
298
|
+
fragColor = encodeMaterial(MAT_EMPTY);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check diagonal falling
|
|
303
|
+
bool canFallThroughLeft = belowLeft == MAT_EMPTY || belowLeft == MAT_WATER;
|
|
304
|
+
bool canFallThroughRight = belowRight == MAT_EMPTY || belowRight == MAT_WATER;
|
|
305
|
+
|
|
306
|
+
bool canFallDownLeft = !atBottom && !atLeft && canFallThroughLeft;
|
|
307
|
+
bool canFallDownRight = !atBottom && !atRight && canFallThroughRight;
|
|
308
|
+
|
|
309
|
+
if (canFallDownLeft || canFallDownRight) {
|
|
310
|
+
fragColor = encodeMaterial(MAT_EMPTY);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Sand stays in place
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case MAT_WATER: {
|
|
319
|
+
// Water - check if being displaced by sand first
|
|
320
|
+
bool sandFallingFromAbove = !atTop && above == MAT_SAND;
|
|
321
|
+
if (sandFallingFromAbove) {
|
|
322
|
+
fragColor = encodeMaterial(MAT_SAND);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check sand sliding into water from diagonals
|
|
327
|
+
bool sandFromAboveLeft = !atTop && !atRight && aboveLeft == MAT_SAND;
|
|
328
|
+
bool sandFromAboveRight = !atTop && !atLeft && aboveRight == MAT_SAND;
|
|
329
|
+
|
|
330
|
+
if (sandFromAboveLeft) {
|
|
331
|
+
vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
|
|
332
|
+
bool leftNotEmpty = getMaterial(left) != MAT_EMPTY;
|
|
333
|
+
bool sandCanSlideIn = leftNotEmpty || atBottom;
|
|
334
|
+
|
|
335
|
+
if (sandCanSlideIn) {
|
|
336
|
+
fragColor = encodeMaterial(MAT_SAND);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (sandFromAboveRight) {
|
|
342
|
+
vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
|
|
343
|
+
bool rightNotEmpty = getMaterial(right) != MAT_EMPTY;
|
|
344
|
+
bool sandCanSlideIn = rightNotEmpty || atBottom;
|
|
345
|
+
|
|
346
|
+
if (sandCanSlideIn) {
|
|
347
|
+
fragColor = encodeMaterial(MAT_SAND);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Normal water physics - falling
|
|
353
|
+
bool canFallDown = !atBottom && below == MAT_EMPTY;
|
|
354
|
+
bool canFlowDownLeft = !atBottom && !atLeft && belowLeft == MAT_EMPTY;
|
|
355
|
+
bool canFlowDownRight = !atBottom && !atRight && belowRight == MAT_EMPTY;
|
|
356
|
+
|
|
357
|
+
if (canFallDown || canFlowDownLeft || canFlowDownRight) {
|
|
358
|
+
fragColor = encodeMaterial(MAT_EMPTY);
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Horizontal spreading
|
|
363
|
+
vec4 left = texture(u_texture, v_uv + DIR_LEFT * u_texelSize);
|
|
364
|
+
vec4 right = texture(u_texture, v_uv + DIR_RIGHT * u_texelSize);
|
|
365
|
+
int matLeft = getMaterial(left);
|
|
366
|
+
int matRight = getMaterial(right);
|
|
367
|
+
|
|
368
|
+
bool canFlowLeft = !atLeft && matLeft == MAT_EMPTY;
|
|
369
|
+
bool canFlowRight = !atRight && matRight == MAT_EMPTY;
|
|
370
|
+
|
|
371
|
+
if (!canFlowLeft && !canFlowRight) {
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Random horizontal movement for fluid behavior
|
|
376
|
+
float rand = fract(sin(dot(v_uv * 1000.0, vec2(12.9898, 78.233))) * 43758.5453);
|
|
377
|
+
bool preferLeft = rand < 0.5;
|
|
378
|
+
|
|
379
|
+
if (
|
|
380
|
+
canFlowLeft && preferLeft ||
|
|
381
|
+
canFlowRight && !preferLeft ||
|
|
382
|
+
canFlowLeft ||
|
|
383
|
+
canFlowDownRight
|
|
384
|
+
) {
|
|
385
|
+
fragColor = encodeMaterial(MAT_EMPTY);
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
case MAT_STONE:
|
|
392
|
+
// Stone doesn't move
|
|
393
|
+
break;
|
|
394
|
+
|
|
395
|
+
default:
|
|
396
|
+
// Unknown material - keep as is
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}`;
|
|
400
|
+
const renderFragment = glsl`#version 300 es
|
|
401
|
+
precision mediump float;
|
|
402
|
+
in vec2 v_uv;
|
|
403
|
+
${uniform.sampler2D("u_texture")}
|
|
404
|
+
${uniform.vec3("u_palette", { size: MATERIAL_SIZE })}
|
|
405
|
+
out vec4 fragColor;
|
|
406
|
+
|
|
407
|
+
// Material constants (must match stepFragment)
|
|
408
|
+
const int MAT_EMPTY = 0;
|
|
409
|
+
const int MAT_SAND = 1;
|
|
410
|
+
const int MAT_WATER = 2;
|
|
411
|
+
const int MAT_STONE = 3;
|
|
412
|
+
|
|
413
|
+
// Helper function to decode material from RGB
|
|
414
|
+
int getMaterial(vec4 color) {
|
|
415
|
+
return int(color.r + color.g * 2.0 + color.b * 4.0);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
void main() {
|
|
419
|
+
vec4 texel = texture(u_texture, v_uv);
|
|
420
|
+
int material = getMaterial(texel);
|
|
421
|
+
|
|
422
|
+
if (material == MAT_EMPTY) {
|
|
423
|
+
fragColor = vec4(0.0, 0.0, 0.0, 1.0); // Black background
|
|
424
|
+
} else if (material > 0 && material <= ${MATERIAL_SIZE}) {
|
|
425
|
+
fragColor = vec4(u_palette[material - 1], 1.0);
|
|
426
|
+
} else {
|
|
427
|
+
fragColor = vec4(1.0, 0.0, 1.0, 1.0); // Magenta for unknown materials
|
|
428
|
+
}
|
|
429
|
+
}`;
|
|
430
|
+
const { program: stepProgram, view: stepView } = compile(gl, vertex, stepFragment);
|
|
431
|
+
const { program: renderProgram, view: renderView } = compile(gl, vertex, renderFragment, {
|
|
432
|
+
schema: {
|
|
433
|
+
attributes: { a_vertex: { buffer: stepView.attributes.a_vertex.buffer } }
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
gl.useProgram(renderProgram);
|
|
437
|
+
renderView.uniforms.u_palette.set(
|
|
438
|
+
new Float32Array(Object.values(MATERIALS).flatMap(({ color }) => color))
|
|
439
|
+
);
|
|
440
|
+
gl.useProgram(stepProgram);
|
|
441
|
+
stepView.attributes.a_vertex.set(new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]));
|
|
442
|
+
const framebufferOptions = {
|
|
443
|
+
attachment: "color",
|
|
444
|
+
width: WIDTH,
|
|
445
|
+
height: HEIGHT,
|
|
446
|
+
type: "UNSIGNED_BYTE",
|
|
447
|
+
magFilter: "NEAREST",
|
|
448
|
+
minFilter: "NEAREST",
|
|
449
|
+
internalFormat: "RGBA",
|
|
450
|
+
format: "RGBA"
|
|
451
|
+
};
|
|
452
|
+
let read = createFramebuffer(gl, framebufferOptions);
|
|
453
|
+
let write = createFramebuffer(gl, framebufferOptions);
|
|
454
|
+
const initData = new Uint8Array(
|
|
455
|
+
function* () {
|
|
456
|
+
for (let i = 0; i < WIDTH * HEIGHT; i++) {
|
|
457
|
+
yield 0;
|
|
458
|
+
yield 0;
|
|
459
|
+
yield 0;
|
|
460
|
+
yield 255;
|
|
461
|
+
}
|
|
462
|
+
}()
|
|
463
|
+
);
|
|
464
|
+
gl.bindTexture(gl.TEXTURE_2D, read.texture);
|
|
465
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, gl.RGBA, gl.UNSIGNED_BYTE, initData);
|
|
466
|
+
gl.bindTexture(gl.TEXTURE_2D, write.texture);
|
|
467
|
+
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, WIDTH, HEIGHT, gl.RGBA, gl.UNSIGNED_BYTE, initData);
|
|
468
|
+
function step() {
|
|
469
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, write.framebuffer);
|
|
470
|
+
gl.viewport(0, 0, WIDTH, HEIGHT);
|
|
471
|
+
gl.useProgram(stepProgram);
|
|
472
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
473
|
+
gl.bindTexture(gl.TEXTURE_2D, read.texture);
|
|
474
|
+
stepView.uniforms.u_texture.set(0);
|
|
475
|
+
stepView.uniforms.u_texelSize.set(1 / WIDTH, 1 / HEIGHT);
|
|
476
|
+
stepView.attributes.a_vertex.bind();
|
|
477
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
478
|
+
}
|
|
479
|
+
function render() {
|
|
480
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
481
|
+
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
|
482
|
+
gl.useProgram(renderProgram);
|
|
483
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
484
|
+
gl.bindTexture(gl.TEXTURE_2D, write.texture);
|
|
485
|
+
renderView.uniforms.u_texture.set(0);
|
|
486
|
+
renderView.attributes.a_vertex.bind();
|
|
487
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
488
|
+
}
|
|
489
|
+
function animate() {
|
|
490
|
+
if (playing) {
|
|
491
|
+
step();
|
|
492
|
+
render();
|
|
493
|
+
[read, write] = [write, read];
|
|
494
|
+
requestAnimationFrame(animate);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
playing = true;
|
|
498
|
+
animate();
|
|
499
|
+
|
|
500
|
+
export { MATERIALS };
|