hypercube-compute 2.0.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/dist/index.js ADDED
@@ -0,0 +1,1392 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AerodynamicsEngine: () => AerodynamicsEngine,
24
+ BlankEngine: () => BlankEngine,
25
+ CanvasAdapter: () => CanvasAdapter,
26
+ EcosystemEngineO1: () => EcosystemEngineO1,
27
+ GameOfLifeEngine: () => GameOfLifeEngine,
28
+ HeatmapEngine: () => HeatmapEngine,
29
+ OceanEngine: () => OceanEngine,
30
+ OceanSimulatorAddon: () => OceanSimulatorAddon,
31
+ OceanWebGLRenderer: () => OceanWebGLRenderer,
32
+ OceanWorld: () => OceanWorld,
33
+ Triade: () => Triade,
34
+ TriadeCubeV2: () => TriadeCubeV2,
35
+ TriadeGrid: () => TriadeGrid,
36
+ TriadeMasterBuffer: () => TriadeMasterBuffer,
37
+ WebGLAdapter: () => WebGLAdapter
38
+ });
39
+ module.exports = __toCommonJS(index_exports);
40
+
41
+ // src/core/TriadeMasterBuffer.ts
42
+ var TriadeMasterBuffer = class {
43
+ buffer;
44
+ offset = 0;
45
+ /**
46
+ * Alloue un unique bloc de mémoire vive (ArrayBuffer) pour l'ensemble du système.
47
+ * @param totalBytes Taille totale de la RAM allouée (par défaut 100 MB).
48
+ */
49
+ constructor(totalBytes = 100 * 1024 * 1024) {
50
+ this.buffer = new ArrayBuffer(totalBytes);
51
+ }
52
+ /**
53
+ * Alloue les octets nécessaires pour un Cube de 6 Faces en O(1) sans fragmentation.
54
+ * @param mapSize Résolution (ex: 400x400)
55
+ * @returns L'offset de départ dans l'ArrayBuffer
56
+ */
57
+ allocateCube(mapSize, numFaces = 6) {
58
+ const floatsPerFace = mapSize * mapSize;
59
+ const bytesPerFace = floatsPerFace * Float32Array.BYTES_PER_ELEMENT;
60
+ const totalCubeBytes = bytesPerFace * numFaces;
61
+ if (this.offset + totalCubeBytes > this.buffer.byteLength) {
62
+ throw new Error(`[TriadeMasterBuffer] Out Of Memory. Impossible d'allouer ${totalCubeBytes} bytes suppl\xE9mentaires.`);
63
+ }
64
+ const startOffset = this.offset;
65
+ this.offset += totalCubeBytes;
66
+ return startOffset;
67
+ }
68
+ /** Retourne la quantité de RAM consommée */
69
+ getUsedMemoryInMB() {
70
+ return (this.offset / (1024 * 1024)).toFixed(2) + " MB";
71
+ }
72
+ };
73
+
74
+ // src/core/TriadeCubeV2.ts
75
+ var TriadeCubeV2 = class {
76
+ mapSize;
77
+ faces = [];
78
+ offset;
79
+ // Added this property
80
+ engine = null;
81
+ /**
82
+ * @param mapSize La résolution (N x N)
83
+ * @param masterBuffer L'allocateur de RAM central de l'Orchestrateur
84
+ */
85
+ constructor(mapSize, masterBuffer, numFaces = 6) {
86
+ this.mapSize = mapSize;
87
+ this.offset = masterBuffer.allocateCube(mapSize, numFaces);
88
+ const floatCount = mapSize * mapSize;
89
+ const bytesPerFace = floatCount * Float32Array.BYTES_PER_ELEMENT;
90
+ for (let i = 0; i < numFaces; i++) {
91
+ this.faces.push(
92
+ new Float32Array(
93
+ masterBuffer.buffer,
94
+ this.offset + i * bytesPerFace,
95
+ floatCount
96
+ )
97
+ );
98
+ }
99
+ }
100
+ /**
101
+ * Injecte le cerveau mathématique dans ce paquet mémoire.
102
+ */
103
+ setEngine(engine) {
104
+ this.engine = engine;
105
+ }
106
+ /**
107
+ * Exécute le calcul de la Frame (O1, Wavefront, Automates)
108
+ */
109
+ compute() {
110
+ if (!this.engine) {
111
+ console.warn("[TriadeCubeV2] Aucun Engine (Cerveau) n'a \xE9t\xE9 assign\xE9 \xE0 ce cube.");
112
+ return;
113
+ }
114
+ this.engine.compute(this.faces, this.mapSize);
115
+ }
116
+ /** Helper pour vider une face spécifique */
117
+ clearFace(faceIndex) {
118
+ this.faces[faceIndex].fill(0);
119
+ }
120
+ };
121
+
122
+ // src/Triade.ts
123
+ var Triade = class {
124
+ _masterBuffer;
125
+ cubes = /* @__PURE__ */ new Map();
126
+ get masterBuffer() {
127
+ return this._masterBuffer;
128
+ }
129
+ /**
130
+ * @param vRamAllocMegabytes Taille du ArrayBuffer en Mega-Octets (par defaut 50MB)
131
+ */
132
+ constructor(vRamAllocMegabytes = 50) {
133
+ this._masterBuffer = new TriadeMasterBuffer(vRamAllocMegabytes * 1024 * 1024);
134
+ console.log(`[Triade.js SDK] Initialized with ${vRamAllocMegabytes}MB of Raw Buffer Memory.`);
135
+ }
136
+ /**
137
+ * Forge un nouveau Cube (View Paging) depuis le Buffer Maître.
138
+ */
139
+ createCube(name, mapSize, engine, numFaces = 6) {
140
+ if (this.cubes.has(name)) {
141
+ throw new Error(`Cube avec le nom ${name} existe d\xE9j\xE0.`);
142
+ }
143
+ const cube = new TriadeCubeV2(mapSize, this._masterBuffer, numFaces);
144
+ cube.setEngine(engine);
145
+ this.cubes.set(name, cube);
146
+ return cube;
147
+ }
148
+ /**
149
+ * Accès sécurisé à un cube pour UI/Render logic.
150
+ */
151
+ getCube(id) {
152
+ return this.cubes.get(id);
153
+ }
154
+ };
155
+
156
+ // src/core/TriadeGrid.ts
157
+ var TriadeGrid = class {
158
+ cubes = [];
159
+ cols;
160
+ rows;
161
+ cubeSize;
162
+ isPeriodic;
163
+ constructor(cols, rows, cubeSize, masterBuffer, engineFactory, numFaces = 6, isPeriodic = true) {
164
+ this.cols = cols;
165
+ this.rows = rows;
166
+ this.cubeSize = cubeSize;
167
+ this.isPeriodic = isPeriodic;
168
+ for (let y = 0; y < rows; y++) {
169
+ this.cubes[y] = [];
170
+ for (let x = 0; x < cols; x++) {
171
+ const cube = new TriadeCubeV2(cubeSize, masterBuffer, numFaces);
172
+ cube.setEngine(engineFactory());
173
+ this.cubes[y][x] = cube;
174
+ }
175
+ }
176
+ }
177
+ /**
178
+ * Calcule une étape complète de la grille.
179
+ * 1. Exécute "compute()" sur chaque cube
180
+ * 2. Synchronise les bords (Boundary Exchange) sur les faces demandées
181
+ */
182
+ compute(facesToSynchronize = 0) {
183
+ for (let y = 0; y < this.rows; y++) {
184
+ for (let x = 0; x < this.cols; x++) {
185
+ this.cubes[y][x]?.compute();
186
+ }
187
+ }
188
+ const faces = Array.isArray(facesToSynchronize) ? facesToSynchronize : [facesToSynchronize];
189
+ for (const f of faces) {
190
+ this.synchronizeBoundaries(f);
191
+ }
192
+ }
193
+ /**
194
+ * Recopie les vecteurs périphériques (1 pixel de profondeur) vers les bords des voisins.
195
+ */
196
+ synchronizeBoundaries(f) {
197
+ const s = this.cubeSize;
198
+ const s_minus_1 = s - 1;
199
+ const s_minus_2 = s - 2;
200
+ for (let y = 0; y < this.rows; y++) {
201
+ for (let x = 0; x < this.cols; x++) {
202
+ const cube = this.cubes[y][x];
203
+ const data = cube.faces[f];
204
+ if (x < this.cols - 1 || this.isPeriodic) {
205
+ const rightCube = this.cubes[y][(x + 1) % this.cols];
206
+ const rightData = rightCube.faces[f];
207
+ for (let row = 1; row < s_minus_1; row++) {
208
+ rightData[row * s + 0] = data[row * s + s_minus_2];
209
+ }
210
+ }
211
+ if (x > 0 || this.isPeriodic) {
212
+ const leftCube = this.cubes[y][(x - 1 + this.cols) % this.cols];
213
+ const leftData = leftCube.faces[f];
214
+ for (let row = 1; row < s_minus_1; row++) {
215
+ leftData[row * s + s_minus_1] = data[row * s + 1];
216
+ }
217
+ }
218
+ }
219
+ }
220
+ for (let y = 0; y < this.rows; y++) {
221
+ for (let x = 0; x < this.cols; x++) {
222
+ const cube = this.cubes[y][x];
223
+ const data = cube.faces[f];
224
+ if (y < this.rows - 1 || this.isPeriodic) {
225
+ const bottomCube = this.cubes[(y + 1) % this.rows][x];
226
+ const bottomData = bottomCube.faces[f];
227
+ bottomData.set(data.subarray(s_minus_2 * s, s_minus_2 * s + s), 0);
228
+ }
229
+ if (y > 0 || this.isPeriodic) {
230
+ const topCube = this.cubes[(y - 1 + this.rows) % this.rows][x];
231
+ const topData = topCube.faces[f];
232
+ topData.set(data.subarray(1 * s, 1 * s + s), s_minus_1 * s);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ };
238
+
239
+ // src/engines/AerodynamicsEngine.ts
240
+ var AerodynamicsEngine = class {
241
+ dragScore = 0;
242
+ initialized = false;
243
+ get name() {
244
+ return "Lattice Boltzmann D2Q9 (O(1))";
245
+ }
246
+ compute(faces, mapSize) {
247
+ const N = mapSize;
248
+ const obstacles = faces[18];
249
+ const ux_out = faces[19];
250
+ const uy_out = faces[20];
251
+ const curl_out = faces[21];
252
+ const cx = [0, 1, 0, -1, 0, 1, -1, -1, 1];
253
+ const cy = [0, 0, 1, 0, -1, 1, 1, -1, -1];
254
+ const w = [4 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, 1 / 36];
255
+ const opp = [0, 3, 4, 1, 2, 7, 8, 5, 6];
256
+ const u0 = 0.12;
257
+ const omega = 1.95;
258
+ if (!this.initialized) {
259
+ for (let idx = 0; idx < N * N; idx++) {
260
+ const rho = 1;
261
+ const ux = u0;
262
+ const uy = 0;
263
+ const u_sq = ux * ux + uy * uy;
264
+ for (let i = 0; i < 9; i++) {
265
+ const cu = cx[i] * ux + cy[i] * uy;
266
+ const feq = w[i] * rho * (1 + 3 * cu + 4.5 * cu * cu - 1.5 * u_sq);
267
+ faces[i][idx] = feq;
268
+ faces[i + 9][idx] = feq;
269
+ }
270
+ }
271
+ this.initialized = true;
272
+ }
273
+ let frameDrag = 0;
274
+ for (let y = 1; y < N - 1; y++) {
275
+ for (let x = 1; x < N - 1; x++) {
276
+ const idx = y * N + x;
277
+ if (obstacles[idx] > 0) {
278
+ for (let i = 1; i < 9; i++) {
279
+ const originX = x - cx[i];
280
+ const originY = y - cy[i];
281
+ const originIdx = originY * N + originX;
282
+ faces[opp[i] + 9][originIdx] = faces[i][originIdx];
283
+ if (i === 1) frameDrag += faces[1][originIdx];
284
+ }
285
+ ux_out[idx] = 0;
286
+ uy_out[idx] = 0;
287
+ continue;
288
+ }
289
+ let rho = 0;
290
+ let ux = 0;
291
+ let uy = 0;
292
+ for (let i = 0; i < 9; i++) {
293
+ const f_val = faces[i][idx];
294
+ rho += f_val;
295
+ ux += cx[i] * f_val;
296
+ uy += cy[i] * f_val;
297
+ }
298
+ if (x === 1) {
299
+ ux = u0 * rho;
300
+ uy = 0;
301
+ }
302
+ if (rho > 0) {
303
+ ux /= rho;
304
+ uy /= rho;
305
+ }
306
+ ux_out[idx] = ux;
307
+ uy_out[idx] = uy;
308
+ const u_sq = ux * ux + uy * uy;
309
+ for (let i = 0; i < 9; i++) {
310
+ const cu = cx[i] * ux + cy[i] * uy;
311
+ const feq = w[i] * rho * (1 + 3 * cu + 4.5 * cu * cu - 1.5 * u_sq);
312
+ const f_post = faces[i][idx] * (1 - omega) + feq * omega;
313
+ let nx = x + cx[i];
314
+ let ny = y + cy[i];
315
+ if (ny < 1) ny = N - 2;
316
+ else if (ny > N - 2) ny = 1;
317
+ if (nx > N - 2) nx = N - 2;
318
+ faces[i + 9][ny * N + nx] = f_post;
319
+ }
320
+ }
321
+ }
322
+ for (let i = 0; i < 9; i++) {
323
+ faces[i].set(faces[i + 9]);
324
+ }
325
+ this.dragScore = this.dragScore * 0.9 + frameDrag * 1e3 * 0.1;
326
+ for (let y = 1; y < N - 1; y++) {
327
+ for (let x = 1; x < N - 1; x++) {
328
+ const idx = y * N + x;
329
+ const dUy_dx = uy_out[idx + 1] - uy_out[idx - 1];
330
+ const dUx_dy = ux_out[idx + N] - ux_out[idx - N];
331
+ curl_out[idx] = dUy_dx - dUx_dy;
332
+ }
333
+ }
334
+ }
335
+ };
336
+
337
+ // src/engines/EcosystemEngineO1.ts
338
+ var EcosystemEngineO1 = class {
339
+ get name() {
340
+ return "Guerre des Triades (Rouge vs Bleu)";
341
+ }
342
+ compute(faces, mapSize) {
343
+ const current = faces[1];
344
+ const next = faces[2];
345
+ for (let y = 0; y < mapSize; y++) {
346
+ for (let x = 0; x < mapSize; x++) {
347
+ const idx = y * mapSize + x;
348
+ const state = current[idx];
349
+ let blues = 0;
350
+ let reds = 0;
351
+ const yM = y > 0 ? y - 1 : mapSize - 1;
352
+ const yP = y < mapSize - 1 ? y + 1 : 0;
353
+ const xM = x > 0 ? x - 1 : mapSize - 1;
354
+ const xP = x < mapSize - 1 ? x + 1 : 0;
355
+ const n1 = current[yM * mapSize + xM];
356
+ if (n1 === 2) blues++;
357
+ else if (n1 === 3) reds++;
358
+ const n2 = current[yM * mapSize + x];
359
+ if (n2 === 2) blues++;
360
+ else if (n2 === 3) reds++;
361
+ const n3 = current[yM * mapSize + xP];
362
+ if (n3 === 2) blues++;
363
+ else if (n3 === 3) reds++;
364
+ const n4 = current[y * mapSize + xM];
365
+ if (n4 === 2) blues++;
366
+ else if (n4 === 3) reds++;
367
+ const n5 = current[y * mapSize + xP];
368
+ if (n5 === 2) blues++;
369
+ else if (n5 === 3) reds++;
370
+ const n6 = current[yP * mapSize + xM];
371
+ if (n6 === 2) blues++;
372
+ else if (n6 === 3) reds++;
373
+ const n7 = current[yP * mapSize + x];
374
+ if (n7 === 2) blues++;
375
+ else if (n7 === 3) reds++;
376
+ const n8 = current[yP * mapSize + xP];
377
+ if (n8 === 2) blues++;
378
+ else if (n8 === 3) reds++;
379
+ const total = blues + reds;
380
+ if (state !== 2 && state !== 3) {
381
+ if (total === 3) {
382
+ next[idx] = blues > reds ? 2 : 3;
383
+ } else if (Math.random() < 5e-4) {
384
+ next[idx] = Math.random() < 0.5 ? 2 : 3;
385
+ } else {
386
+ next[idx] = 0;
387
+ }
388
+ } else if (state === 2) {
389
+ if (reds >= 2) next[idx] = 0;
390
+ else if (total === 2 || total === 3) next[idx] = 2;
391
+ else next[idx] = 0;
392
+ } else if (state === 3) {
393
+ if (blues >= 2) next[idx] = 0;
394
+ else if (total === 2 || total === 3) next[idx] = 3;
395
+ else next[idx] = 0;
396
+ }
397
+ }
398
+ }
399
+ current.set(next);
400
+ }
401
+ };
402
+
403
+ // src/engines/GameOfLifeEngine.ts
404
+ var GameOfLifeEngine = class {
405
+ get name() {
406
+ return "Ecosyst\xE8me Tensoriel (Plantes, Herbivores, Carnivores)";
407
+ }
408
+ compute(faces, mapSize) {
409
+ const current = faces[1];
410
+ const next = faces[2];
411
+ for (let y = 0; y < mapSize; y++) {
412
+ const top = y === 0 ? mapSize - 1 : y - 1;
413
+ const bottom = y === mapSize - 1 ? 0 : y + 1;
414
+ const topRow = top * mapSize;
415
+ const midRow = y * mapSize;
416
+ const botRow = bottom * mapSize;
417
+ for (let x = 0; x < mapSize; x++) {
418
+ const left = x === 0 ? mapSize - 1 : x - 1;
419
+ const right = x === mapSize - 1 ? 0 : x + 1;
420
+ const idx = midRow + x;
421
+ const state = current[idx];
422
+ let targetState = state + 1;
423
+ if (targetState > 3) targetState = 0;
424
+ let predators = 0;
425
+ if (current[topRow + x] === targetState) predators++;
426
+ if (current[midRow + left] === targetState) predators++;
427
+ if (current[midRow + right] === targetState) predators++;
428
+ if (current[botRow + x] === targetState) predators++;
429
+ if (current[topRow + left] === targetState) predators++;
430
+ if (current[topRow + right] === targetState) predators++;
431
+ if (current[botRow + left] === targetState) predators++;
432
+ if (current[botRow + right] === targetState) predators++;
433
+ const threshold = state === 0 ? 1 : 2;
434
+ if (predators >= threshold) {
435
+ next[idx] = targetState;
436
+ } else {
437
+ next[idx] = state;
438
+ }
439
+ }
440
+ }
441
+ current.set(next);
442
+ }
443
+ };
444
+
445
+ // src/engines/HeatmapEngine.ts
446
+ var HeatmapEngine = class {
447
+ name = "Heatmap (O1 Spatial Convolution)";
448
+ radius;
449
+ weight;
450
+ /**
451
+ * @param radius Rayon d'influence en cellules
452
+ * @param weight Coefficient multiplicateur à l'arrivée
453
+ */
454
+ constructor(radius = 10, weight = 1) {
455
+ this.radius = radius;
456
+ this.weight = weight;
457
+ }
458
+ /**
459
+ * Exécute le Summed Area Table Algorithm (Face 5) suivi
460
+ * d'un Box Filter O(1) vers la Synthèse (Face 3).
461
+ */
462
+ compute(faces, mapSize) {
463
+ const face2 = faces[1];
464
+ const face3 = faces[2];
465
+ const face5 = faces[4];
466
+ for (let y = 0; y < mapSize; y++) {
467
+ for (let x = 0; x < mapSize; x++) {
468
+ const idx = y * mapSize + x;
469
+ const val = face2[idx];
470
+ const top = y > 0 ? face5[(y - 1) * mapSize + x] : 0;
471
+ const left = x > 0 ? face5[y * mapSize + (x - 1)] : 0;
472
+ const topLeft = y > 0 && x > 0 ? face5[(y - 1) * mapSize + (x - 1)] : 0;
473
+ face5[idx] = val + top + left - topLeft;
474
+ }
475
+ }
476
+ for (let y = 0; y < mapSize; y++) {
477
+ for (let x = 0; x < mapSize; x++) {
478
+ const minX = Math.max(0, x - this.radius);
479
+ const minY = Math.max(0, y - this.radius);
480
+ const maxX = Math.min(mapSize - 1, x + this.radius);
481
+ const maxY = Math.min(mapSize - 1, y + this.radius);
482
+ const A = minX > 0 && minY > 0 ? face5[(minY - 1) * mapSize + (minX - 1)] : 0;
483
+ const B = minY > 0 ? face5[(minY - 1) * mapSize + maxX] : 0;
484
+ const C = minX > 0 ? face5[maxY * mapSize + (minX - 1)] : 0;
485
+ const D = face5[maxY * mapSize + maxX];
486
+ const sum = D - B - C + A;
487
+ face3[y * mapSize + x] = sum * this.weight;
488
+ }
489
+ }
490
+ }
491
+ };
492
+
493
+ // src/io/CanvasAdapter.ts
494
+ var CanvasAdapter = class {
495
+ /**
496
+ * Lit un Tenseur Plat (Face de Float32Array) et le peint sur un contexte Canvas Native.
497
+ * Cette interface sépare la logique de rendu (UI) du moteur mathématique (Triade).
498
+ */
499
+ static renderFaceToCanvas(faceData, mapSize, ctx, options = { colorScheme: "grayscale" }) {
500
+ const imgData = ctx.getImageData(0, 0, mapSize, mapSize);
501
+ const data = imgData.data;
502
+ let max = options.normalizeMax || 1e-4;
503
+ if (!options.normalizeMax) {
504
+ for (let i = 0; i < faceData.length; i++) {
505
+ if (faceData[i] > max) max = faceData[i];
506
+ }
507
+ }
508
+ for (let i = 0; i < faceData.length; i++) {
509
+ const val = faceData[i] / max;
510
+ const p = i * 4;
511
+ if (options.colorScheme === "heat") {
512
+ data[p] = val * 255;
513
+ data[p + 1] = val > 0.5 ? (val - 0.5) * 510 : 0;
514
+ data[p + 2] = val * 50;
515
+ data[p + 3] = 255;
516
+ } else {
517
+ const c = val * 255;
518
+ data[p] = c;
519
+ data[p + 1] = c;
520
+ data[p + 2] = c;
521
+ data[p + 3] = 255;
522
+ }
523
+ }
524
+ ctx.putImageData(imgData, 0, 0);
525
+ }
526
+ };
527
+
528
+ // src/io/WebGLAdapter.ts
529
+ var WebGLAdapter = class {
530
+ gl;
531
+ texture;
532
+ program;
533
+ mapSize;
534
+ constructor(canvas, mapSize) {
535
+ this.mapSize = mapSize;
536
+ const gl = canvas.getContext("webgl2");
537
+ if (!gl) {
538
+ throw new Error("WebGL2 n'est pas support\xE9 par ce navigateur.");
539
+ }
540
+ this.gl = gl;
541
+ const vsSource = `#version 300 es
542
+ in vec2 a_position;
543
+ out vec2 v_uv;
544
+ void main() {
545
+ v_uv = a_position * 0.5 + 0.5; // conversion -1,1 vers 0,1
546
+ v_uv.y = 1.0 - v_uv.y; // Flip Y
547
+ gl_Position = vec4(a_position, 0.0, 1.0);
548
+ }`;
549
+ const fsSource = `#version 300 es
550
+ precision highp float;
551
+ precision highp sampler2D;
552
+
553
+ in vec2 v_uv;
554
+ uniform sampler2D u_tensor;
555
+ out vec4 outColor;
556
+
557
+ // Palette simple: froid (bleu) -> chaud (rouge)
558
+ vec3 heatMap(float t) {
559
+ return clamp(vec3(1.5*t, 2.0*t - 0.5, 0.5 - t*1.5), 0.0, 1.0);
560
+ }
561
+
562
+ void main() {
563
+ // Lecture du Float32Array exact depuis la VRAM Triade
564
+ float val = texture(u_tensor, v_uv).r;
565
+
566
+ // Affichage avec la palette
567
+ vec3 color = mix(vec3(0.05, 0.07, 0.1), vec3(0.0, 0.8, 1.0), clamp(val, 0.0, 1.0)); // Custom Cyan/Dark mapping
568
+ if (val > 1.0) color = vec3(1.0, 1.0, 1.0); // Saturation
569
+
570
+ outColor = vec4(color, 1.0);
571
+ }`;
572
+ this.program = this.createProgram(vsSource, fsSource);
573
+ this.gl.useProgram(this.program);
574
+ const positionBuffer = this.gl.createBuffer();
575
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer);
576
+ const positions = new Float32Array([
577
+ -1,
578
+ -1,
579
+ 1,
580
+ -1,
581
+ -1,
582
+ 1,
583
+ -1,
584
+ 1,
585
+ 1,
586
+ -1,
587
+ 1,
588
+ 1
589
+ ]);
590
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW);
591
+ const posLoc = this.gl.getAttribLocation(this.program, "a_position");
592
+ this.gl.enableVertexAttribArray(posLoc);
593
+ this.gl.vertexAttribPointer(posLoc, 2, this.gl.FLOAT, false, 0, 0);
594
+ this.gl.getExtension("EXT_color_buffer_float");
595
+ this.gl.getExtension("OES_texture_float_linear");
596
+ this.texture = this.gl.createTexture();
597
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
598
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);
599
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
600
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
601
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
602
+ this.gl.texImage2D(
603
+ this.gl.TEXTURE_2D,
604
+ 0,
605
+ this.gl.R32F,
606
+ mapSize,
607
+ mapSize,
608
+ 0,
609
+ this.gl.RED,
610
+ this.gl.FLOAT,
611
+ null
612
+ );
613
+ }
614
+ /**
615
+ * Propulse la Face Mémoire Triade directement dans la VRAM GPU.
616
+ * C'est l'opération la plus rapide sur navigateur (O(1) Data Upload).
617
+ */
618
+ renderFaceToWebGL(faceData) {
619
+ this.gl.useProgram(this.program);
620
+ this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
621
+ this.gl.texSubImage2D(
622
+ this.gl.TEXTURE_2D,
623
+ 0,
624
+ 0,
625
+ 0,
626
+ this.mapSize,
627
+ this.mapSize,
628
+ this.gl.RED,
629
+ this.gl.FLOAT,
630
+ faceData
631
+ );
632
+ this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
633
+ }
634
+ createProgram(vsSource, fsSource) {
635
+ const vs = this.gl.createShader(this.gl.VERTEX_SHADER);
636
+ this.gl.shaderSource(vs, vsSource);
637
+ this.gl.compileShader(vs);
638
+ const fs = this.gl.createShader(this.gl.FRAGMENT_SHADER);
639
+ this.gl.shaderSource(fs, fsSource);
640
+ this.gl.compileShader(fs);
641
+ const prog = this.gl.createProgram();
642
+ this.gl.attachShader(prog, vs);
643
+ this.gl.attachShader(prog, fs);
644
+ this.gl.linkProgram(prog);
645
+ if (!this.gl.getProgramParameter(prog, this.gl.LINK_STATUS)) {
646
+ console.error(this.gl.getProgramInfoLog(prog));
647
+ throw new Error("Erreur de compilation WebGL");
648
+ }
649
+ return prog;
650
+ }
651
+ };
652
+
653
+ // src/addons/ocean-simulation/OceanEngine.ts
654
+ var OceanEngine = class {
655
+ name = "OceanEngine";
656
+ // Re-use lab-perfect constants
657
+ w = [4 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, 1 / 36];
658
+ cx = [0, 1, 0, -1, 0, 1, -1, -1, 1];
659
+ cy = [0, 0, 1, 0, -1, 1, 1, -1, -1];
660
+ opp = [0, 3, 4, 1, 2, 7, 8, 5, 6];
661
+ // Caches to avoid per-frame allocations
662
+ feq_cache = new Float32Array(9);
663
+ pulled_f = new Float32Array(9);
664
+ params = {
665
+ tau_0: 0.8,
666
+ smagorinsky: 0.2,
667
+ cflLimit: 0.38,
668
+ bioDiffusion: 0.05,
669
+ bioGrowth: 5e-4,
670
+ vortexRadius: 28,
671
+ vortexStrength: 0.02
672
+ };
673
+ stats = {
674
+ maxU: 0,
675
+ avgTau: 0
676
+ };
677
+ // UI Input simulation (will be fed by the high-level framework/addon)
678
+ interaction = {
679
+ mouseX: 0,
680
+ mouseY: 0,
681
+ active: false
682
+ };
683
+ constructor() {
684
+ }
685
+ /**
686
+ * Entry point: Orchestrates LBM and Bio steps
687
+ */
688
+ compute(faces, size) {
689
+ this.stepLBM(faces, size);
690
+ this.stepBio(faces, size);
691
+ }
692
+ stepLBM(m, size) {
693
+ const rho = m[20], ux = m[18], uy = m[19], obst = m[22];
694
+ let maxU = 0;
695
+ let sumTau = 0;
696
+ let activeCells = 0;
697
+ for (let k = 0; k < 9; k++) m[k + 9].fill(0);
698
+ const mx = this.interaction.mouseX;
699
+ const my = this.interaction.mouseY;
700
+ const isForcing = this.interaction.active;
701
+ const vr2 = this.params.vortexRadius * this.params.vortexRadius;
702
+ for (let y = 1; y < size - 1; y++) {
703
+ for (let x = 1; x < size - 1; x++) {
704
+ const i = y * size + x;
705
+ if (obst[i] > 0.5) {
706
+ for (let k = 0; k < 9; k++) m[k + 9][i] = this.w[k];
707
+ continue;
708
+ }
709
+ let r = 0, vx = 0, vy = 0;
710
+ for (let k = 0; k < 9; k++) {
711
+ const nx = x - this.cx[k];
712
+ const ny = y - this.cy[k];
713
+ const ni = ny * size + nx;
714
+ if (obst[ni] > 0.5) {
715
+ this.pulled_f[k] = m[this.opp[k]][i];
716
+ } else {
717
+ this.pulled_f[k] = m[k][ni];
718
+ }
719
+ r += this.pulled_f[k];
720
+ vx += this.pulled_f[k] * this.cx[k];
721
+ vy += this.pulled_f[k] * this.cy[k];
722
+ }
723
+ let isShockwave = false;
724
+ if (r < 0.8) {
725
+ r = 0.8;
726
+ isShockwave = true;
727
+ } else if (r > 1.2) {
728
+ r = 1.2;
729
+ isShockwave = true;
730
+ } else if (r < 1e-4) r = 1e-4;
731
+ vx /= r;
732
+ vy /= r;
733
+ if (isForcing) {
734
+ const dx = x - mx;
735
+ const dy = y - my;
736
+ const dist2 = dx * dx + dy * dy;
737
+ if (dist2 < vr2) {
738
+ const forceScale = this.params.vortexStrength * 5e-3 * (1 - Math.sqrt(dist2) / this.params.vortexRadius);
739
+ vx += -dy * forceScale;
740
+ vy += dx * forceScale;
741
+ }
742
+ }
743
+ const v2 = vx * vx + vy * vy;
744
+ const speed = Math.sqrt(v2);
745
+ if (speed > maxU) maxU = speed;
746
+ let u2_clamped = v2;
747
+ if (speed > this.params.cflLimit) {
748
+ const scale = this.params.cflLimit / speed;
749
+ vx *= scale;
750
+ vy *= scale;
751
+ u2_clamped = vx * vx + vy * vy;
752
+ isShockwave = true;
753
+ }
754
+ rho[i] = r;
755
+ ux[i] = vx;
756
+ uy[i] = vy;
757
+ if (isShockwave) {
758
+ for (let k = 0; k < 9; k++) {
759
+ const cu = 3 * (this.cx[k] * vx + this.cy[k] * vy);
760
+ m[k + 9][i] = this.w[k] * r * (1 + cu + 0.5 * cu * cu - 1.5 * u2_clamped);
761
+ }
762
+ } else {
763
+ let Pxx = 0, Pyy = 0, Pxy = 0;
764
+ for (let k = 0; k < 9; k++) {
765
+ const cu = 3 * (this.cx[k] * vx + this.cy[k] * vy);
766
+ this.feq_cache[k] = this.w[k] * r * (1 + cu + 0.5 * cu * cu - 1.5 * u2_clamped);
767
+ const fneq = this.pulled_f[k] - this.feq_cache[k];
768
+ Pxx += fneq * this.cx[k] * this.cx[k];
769
+ Pyy += fneq * this.cy[k] * this.cy[k];
770
+ Pxy += fneq * this.cx[k] * this.cy[k];
771
+ }
772
+ const S_norm = Math.sqrt(2 * (Pxx * Pxx + Pyy * Pyy + 2 * Pxy * Pxy));
773
+ let tau_eff = this.params.tau_0 + this.params.smagorinsky * S_norm;
774
+ if (isNaN(tau_eff) || tau_eff < 0.505) tau_eff = 0.505;
775
+ sumTau += tau_eff;
776
+ activeCells++;
777
+ for (let k = 0; k < 9; k++) {
778
+ m[k + 9][i] = this.pulled_f[k] - (this.pulled_f[k] - this.feq_cache[k]) / tau_eff;
779
+ }
780
+ }
781
+ }
782
+ }
783
+ if (activeCells > 0) this.stats.avgTau = sumTau / activeCells;
784
+ this.stats.maxU = maxU;
785
+ for (let k = 0; k < 9; k++) {
786
+ const tmp = m[k];
787
+ m[k] = m[k + 9];
788
+ m[k + 9] = tmp;
789
+ }
790
+ }
791
+ stepBio(m, size) {
792
+ const bio = m[21];
793
+ const bio_next = m[17];
794
+ const area = size * size;
795
+ for (let y = 1; y < size - 1; y++) {
796
+ for (let x = 1; x < size - 1; x++) {
797
+ const i = y * size + x;
798
+ const lap = bio[i - 1] + bio[i + 1] + bio[i - size] + bio[i + size] - 4 * bio[i];
799
+ let next = bio[i] + this.params.bioDiffusion * lap + this.params.bioGrowth * bio[i] * (1 - bio[i]);
800
+ if (next < 0) next = 0;
801
+ if (next > 1) next = 1;
802
+ bio_next[i] = next;
803
+ }
804
+ }
805
+ for (let i = 0; i < area; i++) bio[i] = bio_next[i];
806
+ }
807
+ };
808
+
809
+ // src/addons/ocean-simulation/OceanSimulatorAddon.ts
810
+ var OceanSimulatorAddon = class {
811
+ size;
812
+ master;
813
+ cube;
814
+ engine;
815
+ boats = [];
816
+ constructor(size = 256) {
817
+ this.size = size;
818
+ this.master = new TriadeMasterBuffer();
819
+ this.cube = new TriadeCubeV2(size, this.master, 24);
820
+ this.engine = new OceanEngine();
821
+ this.cube.setEngine(this.engine);
822
+ const w = [4 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, 1 / 36];
823
+ for (let k = 0; k < 9; k++) {
824
+ this.cube.faces[k].fill(w[k]);
825
+ }
826
+ }
827
+ /**
828
+ * Engine parameters access
829
+ */
830
+ get params() {
831
+ return this.engine.params;
832
+ }
833
+ get stats() {
834
+ return this.engine.stats;
835
+ }
836
+ /**
837
+ * High-level Boat API
838
+ */
839
+ addBoat(x, y, length = 20) {
840
+ this.boats.push({ x, y, vx: 0, vy: 0, length, angle: 0 });
841
+ }
842
+ /**
843
+ * Global Step: Fluid -> Bio -> Boats
844
+ */
845
+ step() {
846
+ this.cube.compute();
847
+ const ux = this.cube.faces[18];
848
+ const uy = this.cube.faces[19];
849
+ const size = this.size;
850
+ for (const boat of this.boats) {
851
+ const ix = Math.floor(boat.x);
852
+ const iy = Math.floor(boat.y);
853
+ if (ix >= 0 && ix < size && iy >= 0 && iy < size) {
854
+ const i = iy * size + ix;
855
+ const fux = ux[i];
856
+ const fuy = uy[i];
857
+ boat.vx += fux * 0.15 - boat.vx * 0.05;
858
+ boat.vy += fuy * 0.15 - boat.vy * 0.05;
859
+ boat.x += boat.vx;
860
+ boat.y += boat.vy;
861
+ if (Math.abs(boat.vx) > 0.01 || Math.abs(boat.vy) > 0.01) {
862
+ boat.angle = Math.atan2(boat.vy, boat.vx);
863
+ }
864
+ boat.x = (boat.x + size) % size;
865
+ boat.y = (boat.y + size) % size;
866
+ }
867
+ }
868
+ }
869
+ /**
870
+ * Interaction bridge
871
+ */
872
+ setInteraction(x, y, active) {
873
+ this.engine.interaction.mouseX = x;
874
+ this.engine.interaction.mouseY = y;
875
+ this.engine.interaction.active = active;
876
+ }
877
+ /**
878
+ * Helper to render everything to a canvas
879
+ */
880
+ render(ctx, imageData) {
881
+ const size = this.size;
882
+ const bio = this.cube.faces[21];
883
+ const ux = this.cube.faces[18];
884
+ const uy = this.cube.faces[19];
885
+ const obst = this.cube.faces[22];
886
+ const data = imageData.data;
887
+ for (let i = 0; i < size * size; i++) {
888
+ const j = i * 4;
889
+ if (obst[i] > 0.5) {
890
+ data[j] = 20;
891
+ data[j + 1] = 20;
892
+ data[j + 2] = 40;
893
+ data[j + 3] = 255;
894
+ } else {
895
+ const speed = Math.sqrt(ux[i] * ux[i] + uy[i] * uy[i]);
896
+ const intensity = speed * 1200;
897
+ data[j] = Math.min(255, bio[i] * 40 + intensity * 0.1);
898
+ data[j + 1] = Math.min(255, bio[i] * 120 + intensity * 0.4);
899
+ data[j + 2] = Math.min(255, intensity * 2.5 + 20);
900
+ data[j + 3] = 255;
901
+ }
902
+ }
903
+ ctx.putImageData(imageData, 0, 0);
904
+ ctx.fillStyle = "#fff";
905
+ for (const boat of this.boats) {
906
+ ctx.save();
907
+ ctx.translate(boat.x, boat.y);
908
+ ctx.rotate(boat.angle);
909
+ ctx.fillRect(-boat.length / 2, -2, boat.length, 4);
910
+ ctx.restore();
911
+ }
912
+ }
913
+ /** Access to raw buffers for custom rendering */
914
+ getRawFaces() {
915
+ return this.cube.faces;
916
+ }
917
+ };
918
+
919
+ // src/addons/ocean-simulation/OceanWebGLRenderer.ts
920
+ var OceanWebGLRenderer = class {
921
+ gl;
922
+ program;
923
+ indexCount = 0;
924
+ texRho;
925
+ texUx;
926
+ texUy;
927
+ texObst;
928
+ globalW;
929
+ globalH;
930
+ chunkSize;
931
+ constructor(canvas, cols, rows, chunkSize) {
932
+ this.chunkSize = chunkSize;
933
+ this.globalW = cols * chunkSize;
934
+ this.globalH = rows * chunkSize;
935
+ const gl = canvas.getContext("webgl2");
936
+ if (!gl) throw new Error("WebGL2 non support\xE9.");
937
+ this.gl = gl;
938
+ this.gl.getExtension("EXT_color_buffer_float");
939
+ this.gl.getExtension("OES_texture_float_linear");
940
+ const vsSource = `#version 300 es
941
+ in vec2 a_position;
942
+ out vec2 v_uv;
943
+ out vec3 v_worldPos;
944
+ uniform sampler2D u_rho; // We sample density in Vertex Shader!
945
+ uniform sampler2D u_obst; // Also sample obstacles in Vertex Shader!
946
+
947
+ void main() {
948
+ v_uv = a_position * 0.5 + 0.5;
949
+ v_uv.y = 1.0 - v_uv.y;
950
+
951
+ // VTF: Vertex Texture Fetch (Real 3D heights from LBM Density)
952
+ float rho = texture(u_rho, v_uv).r;
953
+ float obst = texture(u_obst, v_uv).r;
954
+ float h = 0.0;
955
+ if (obst > 0.5) {
956
+ h = 5.0; // Island elevated plateau
957
+ } else {
958
+ h = (rho - 1.0) * 8.0; // Wave extrusion amplitude (softened)
959
+ }
960
+
961
+ vec3 p = vec3(a_position.x, a_position.y, h);
962
+ v_worldPos = p;
963
+
964
+ // 3D Camera / Perspective Math
965
+ float angleX = -1.1; // Look down tilt
966
+ float cosX = cos(angleX);
967
+ float sinX = sin(angleX);
968
+
969
+ // Apply Pitch rotation
970
+ float yRot = p.y * cosX - p.z * sinX;
971
+ float zRot = p.y * sinX + p.z * cosX;
972
+
973
+ // Move camera back
974
+ zRot -= 2.2;
975
+
976
+ // Perspective projection
977
+ float fov = 1.7;
978
+ gl_Position = vec4(p.x * fov, (yRot - 0.2) * fov, zRot, -zRot);
979
+ }`;
980
+ const fsSource = `#version 300 es
981
+ precision highp float;
982
+ precision highp sampler2D;
983
+
984
+ in vec2 v_uv;
985
+ in vec3 v_worldPos;
986
+ uniform sampler2D u_rho; // LBM Density -> Heights
987
+ uniform sampler2D u_ux;
988
+ uniform sampler2D u_uy;
989
+ uniform sampler2D u_obst;
990
+
991
+ out vec4 outColor;
992
+
993
+ void main() {
994
+ float obst = texture(u_obst, v_uv).r;
995
+ if (obst > 0.5) {
996
+ // Island material
997
+ vec3 islandTop = vec3(0.5, 0.6, 0.3); // Grass / Forest
998
+ vec3 islandSide = vec3(0.7, 0.6, 0.4); // Sand / Rock
999
+
1000
+ // Read local normals to detect cliffs
1001
+ float oL = textureOffset(u_obst, v_uv, ivec2(-1, 0)).r;
1002
+ float oR = textureOffset(u_obst, v_uv, ivec2(1, 0)).r;
1003
+ float oB = textureOffset(u_obst, v_uv, ivec2(0, -1)).r;
1004
+ float oT = textureOffset(u_obst, v_uv, ivec2(0, 1)).r;
1005
+ float sumO = oL + oR + oB + oT;
1006
+
1007
+ if (sumO < 3.5) {
1008
+ outColor = vec4(islandSide, 1.0); // Cliff / Beach
1009
+ } else {
1010
+ outColor = vec4(islandTop, 1.0); // Plateau core
1011
+ }
1012
+ return;
1013
+ }
1014
+
1015
+ // Sample density around the pixel to compute normal vector
1016
+ float rL = textureOffset(u_rho, v_uv, ivec2(-1, 0)).r;
1017
+ float rR = textureOffset(u_rho, v_uv, ivec2(1, 0)).r;
1018
+ float rB = textureOffset(u_rho, v_uv, ivec2(0, -1)).r; // Inverted Y due to texture flip
1019
+ float rT = textureOffset(u_rho, v_uv, ivec2(0, 1)).r;
1020
+
1021
+ // Generate Normal from heightmap (Density is height proxy)
1022
+ float strength = 25.0; // Softened normals
1023
+ vec3 normal = normalize(vec3((rL - rR) * strength, (rB - rT) * strength, 1.0));
1024
+
1025
+ // Lighting setup
1026
+ vec3 lightDir = normalize(vec3(0.5, 0.8, 0.5)); // Sun angle
1027
+ vec3 viewDir = normalize(vec3(0.0, -0.6, 1.0)); // Looking at the water
1028
+
1029
+ float ux = texture(u_ux, v_uv).r;
1030
+ float uy = texture(u_uy, v_uv).r;
1031
+ float speed = sqrt(ux*ux + uy*uy);
1032
+
1033
+ // Water Base Color based on speed (currents)
1034
+ vec3 waterDeep = vec3(0.02, 0.2, 0.5);
1035
+ vec3 waterShallow = vec3(0.0, 0.6, 0.9);
1036
+ vec3 waterColor = mix(waterDeep, waterShallow, clamp(speed * 80.0, 0.0, 1.0));
1037
+
1038
+ // Diffuse Lighting (Lambert)
1039
+ float diff = max(dot(normal, lightDir), 0.0);
1040
+ vec3 diffuse = waterColor * (diff * 0.7 + 0.3); // + ambient
1041
+
1042
+ // Specular Lighting (Phong)
1043
+ vec3 reflectDir = reflect(-lightDir, normal);
1044
+ float spec = pow(max(dot(viewDir, reflectDir), 0.0), 48.0);
1045
+ vec3 specular = vec3(1.0, 1.0, 1.0) * spec * 0.8;
1046
+
1047
+ // Foam based on density peaks (breaking waves)
1048
+ float rho_center = texture(u_rho, v_uv).r;
1049
+ float foam = (rho_center > 1.015) ? clamp((rho_center - 1.015) * 15.0, 0.0, 1.0) : 0.0;
1050
+
1051
+ outColor = vec4(diffuse + specular + foam, 1.0);
1052
+ }`;
1053
+ this.program = this.createProgram(vsSource, fsSource);
1054
+ const segments = 256;
1055
+ const vertices = new Float32Array((segments + 1) * (segments + 1) * 2);
1056
+ let ptr = 0;
1057
+ for (let y = 0; y <= segments; y++) {
1058
+ for (let x = 0; x <= segments; x++) {
1059
+ vertices[ptr++] = x / segments * 2 - 1;
1060
+ vertices[ptr++] = 1 - y / segments * 2;
1061
+ }
1062
+ }
1063
+ const indices = new Uint32Array(segments * segments * 6);
1064
+ let iPtr = 0;
1065
+ for (let y = 0; y < segments; y++) {
1066
+ for (let x = 0; x < segments; x++) {
1067
+ const i = x + y * (segments + 1);
1068
+ indices[iPtr++] = i;
1069
+ indices[iPtr++] = i + 1;
1070
+ indices[iPtr++] = i + segments + 1;
1071
+ indices[iPtr++] = i + segments + 1;
1072
+ indices[iPtr++] = i + 1;
1073
+ indices[iPtr++] = i + segments + 2;
1074
+ }
1075
+ }
1076
+ this.indexCount = indices.length;
1077
+ const posBuf = this.gl.createBuffer();
1078
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, posBuf);
1079
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
1080
+ const indexBuf = this.gl.createBuffer();
1081
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuf);
1082
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, indices, this.gl.STATIC_DRAW);
1083
+ this.texRho = this.createTexture();
1084
+ this.texUx = this.createTexture();
1085
+ this.texUy = this.createTexture();
1086
+ this.texObst = this.createTexture();
1087
+ this.gl.useProgram(this.program);
1088
+ this.bindTextureUniform("u_rho", 0);
1089
+ this.bindTextureUniform("u_ux", 1);
1090
+ this.bindTextureUniform("u_uy", 2);
1091
+ this.bindTextureUniform("u_obst", 3);
1092
+ const posLoc = this.gl.getAttribLocation(this.program, "a_position");
1093
+ this.gl.enableVertexAttribArray(posLoc);
1094
+ this.gl.vertexAttribPointer(posLoc, 2, this.gl.FLOAT, false, 0, 0);
1095
+ }
1096
+ createTexture() {
1097
+ const tex = this.gl.createTexture();
1098
+ this.gl.bindTexture(this.gl.TEXTURE_2D, tex);
1099
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
1100
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
1101
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
1102
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
1103
+ this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R32F, this.globalW, this.globalH, 0, this.gl.RED, this.gl.FLOAT, null);
1104
+ return tex;
1105
+ }
1106
+ bindTextureUniform(name, unit) {
1107
+ const loc = this.gl.getUniformLocation(this.program, name);
1108
+ this.gl.uniform1i(loc, unit);
1109
+ }
1110
+ updateSubTexture(tex, unit, xOff, yOff, data) {
1111
+ this.gl.activeTexture(this.gl.TEXTURE0 + unit);
1112
+ this.gl.bindTexture(this.gl.TEXTURE_2D, tex);
1113
+ this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, xOff, yOff, this.chunkSize, this.chunkSize, this.gl.RED, this.gl.FLOAT, data);
1114
+ }
1115
+ render(grid) {
1116
+ this.gl.useProgram(this.program);
1117
+ for (let y = 0; y < grid.rows; y++) {
1118
+ for (let x = 0; x < grid.cols; x++) {
1119
+ const cube = grid.cubes[y][x];
1120
+ if (!cube) continue;
1121
+ const xOff = x * this.chunkSize;
1122
+ const yOff = y * this.chunkSize;
1123
+ this.updateSubTexture(this.texRho, 0, xOff, yOff, cube.faces[20]);
1124
+ this.updateSubTexture(this.texUx, 1, xOff, yOff, cube.faces[18]);
1125
+ this.updateSubTexture(this.texUy, 2, xOff, yOff, cube.faces[19]);
1126
+ this.updateSubTexture(this.texObst, 3, xOff, yOff, cube.faces[22]);
1127
+ }
1128
+ }
1129
+ this.gl.drawElements(this.gl.TRIANGLES, this.indexCount, this.gl.UNSIGNED_INT, 0);
1130
+ }
1131
+ createProgram(vsSource, fsSource) {
1132
+ const vs = this.gl.createShader(this.gl.VERTEX_SHADER);
1133
+ this.gl.shaderSource(vs, vsSource);
1134
+ this.gl.compileShader(vs);
1135
+ const fs = this.gl.createShader(this.gl.FRAGMENT_SHADER);
1136
+ this.gl.shaderSource(fs, fsSource);
1137
+ this.gl.compileShader(fs);
1138
+ const prog = this.gl.createProgram();
1139
+ this.gl.attachShader(prog, vs);
1140
+ this.gl.attachShader(prog, fs);
1141
+ this.gl.linkProgram(prog);
1142
+ return prog;
1143
+ }
1144
+ };
1145
+
1146
+ // src/addons/ocean-simulation/OceanWorld.ts
1147
+ var OceanWorld = class {
1148
+ cols;
1149
+ rows;
1150
+ chunkSize;
1151
+ globalSizeW;
1152
+ globalSizeH;
1153
+ grid;
1154
+ boats = [];
1155
+ keys = { up: false, down: false, left: false, right: false };
1156
+ constructor(masterBuffer, cols = 2, rows = 2, chunkSize = 64) {
1157
+ this.cols = cols;
1158
+ this.rows = rows;
1159
+ this.chunkSize = chunkSize;
1160
+ this.globalSizeW = cols * chunkSize;
1161
+ this.globalSizeH = rows * chunkSize;
1162
+ this.grid = new TriadeGrid(cols, rows, chunkSize, masterBuffer, () => new OceanEngine(), 24, true);
1163
+ this.reset();
1164
+ }
1165
+ reset() {
1166
+ const w = [4 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, 1 / 36];
1167
+ for (let y = 0; y < this.rows; y++) {
1168
+ for (let x = 0; x < this.cols; x++) {
1169
+ const cube = this.grid.cubes[y][x];
1170
+ cube.faces[20].fill(1);
1171
+ cube.faces[18].fill(0);
1172
+ cube.faces[19].fill(0);
1173
+ for (let k = 0; k < 9; k++) {
1174
+ cube.faces[k].fill(w[k]);
1175
+ cube.faces[k + 9].fill(w[k]);
1176
+ }
1177
+ }
1178
+ }
1179
+ const cx = this.globalSizeW / 2;
1180
+ const cy = this.globalSizeH / 2;
1181
+ for (let y = 0; y < this.rows; y++) {
1182
+ for (let x = 0; x < this.cols; x++) {
1183
+ const cube = this.grid.cubes[y][x];
1184
+ const obst = cube.faces[22];
1185
+ obst.fill(0);
1186
+ for (let ly = 0; ly < this.chunkSize; ly++) {
1187
+ for (let lx = 0; lx < this.chunkSize; lx++) {
1188
+ const globalX = x * this.chunkSize + lx;
1189
+ const globalY = y * this.chunkSize + ly;
1190
+ const dx = globalX - cx;
1191
+ const dy = globalY - cy;
1192
+ if (dx * dx + dy * dy < 400) {
1193
+ obst[ly * this.chunkSize + lx] = 1;
1194
+ }
1195
+ }
1196
+ }
1197
+ }
1198
+ }
1199
+ for (let b of this.boats) {
1200
+ b.x = 20;
1201
+ b.y = 20;
1202
+ b.vx = 0;
1203
+ b.vy = 0;
1204
+ b.angle = 0;
1205
+ }
1206
+ this.grid.compute([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22]);
1207
+ }
1208
+ addBoat(globalX, globalY, length = 20) {
1209
+ this.boats.push({ x: globalX, y: globalY, vx: 0, vy: 0, length, angle: 0 });
1210
+ }
1211
+ step() {
1212
+ this.grid.compute([0, 1, 2, 3, 4, 5, 6, 7, 8, 18, 19, 20, 21]);
1213
+ for (const boat of this.boats) {
1214
+ const gx = Math.floor(boat.x);
1215
+ const gy = Math.floor(boat.y);
1216
+ const safeGx = (gx + this.globalSizeW) % this.globalSizeW;
1217
+ const safeGy = (gy + this.globalSizeH) % this.globalSizeH;
1218
+ const chunkX = Math.floor(safeGx / this.chunkSize);
1219
+ const chunkY = Math.floor(safeGy / this.chunkSize);
1220
+ const localX = safeGx % this.chunkSize;
1221
+ const localY = safeGy % this.chunkSize;
1222
+ const cube = this.grid.cubes[chunkY][chunkX];
1223
+ if (cube) {
1224
+ const uxFace = cube.faces[18];
1225
+ const uyFace = cube.faces[19];
1226
+ const localIdx = localY * this.chunkSize + localX;
1227
+ const fux = uxFace[localIdx];
1228
+ const fuy = uyFace[localIdx];
1229
+ const engineForce = 0.15;
1230
+ const turnForce = 0.05;
1231
+ if (this.keys.up) {
1232
+ boat.vx += Math.cos(boat.angle) * engineForce;
1233
+ boat.vy += Math.sin(boat.angle) * engineForce;
1234
+ }
1235
+ if (this.keys.down) {
1236
+ boat.vx -= Math.cos(boat.angle) * engineForce * 0.5;
1237
+ boat.vy -= Math.sin(boat.angle) * engineForce * 0.5;
1238
+ }
1239
+ if (this.keys.left) boat.angle -= turnForce;
1240
+ if (this.keys.right) boat.angle += turnForce;
1241
+ if (!this.keys.up && !this.keys.down && !this.keys.left && !this.keys.right) {
1242
+ boat.vx += Math.cos(boat.angle) * 0.01;
1243
+ boat.vy += Math.sin(boat.angle) * 0.01;
1244
+ }
1245
+ boat.vx += fux * 0.25 - boat.vx * 0.05;
1246
+ boat.vy += fuy * 0.25 - boat.vy * 0.05;
1247
+ boat.x += boat.vx;
1248
+ boat.y += boat.vy;
1249
+ let cDx = boat.x - this.globalSizeW / 2;
1250
+ if (Math.abs(cDx) > this.globalSizeW / 2) cDx = cDx > 0 ? cDx - this.globalSizeW : cDx + this.globalSizeW;
1251
+ let cDy = boat.y - this.globalSizeH / 2;
1252
+ if (Math.abs(cDy) > this.globalSizeH / 2) cDy = cDy > 0 ? cDy - this.globalSizeH : cDy + this.globalSizeH;
1253
+ const newDist = Math.sqrt(cDx * cDx + cDy * cDy);
1254
+ const islandR = 24;
1255
+ if (newDist < islandR) {
1256
+ const angleFromIsland = Math.atan2(cDy, cDx);
1257
+ boat.x += Math.cos(angleFromIsland) * (islandR - newDist);
1258
+ boat.y += Math.sin(angleFromIsland) * (islandR - newDist);
1259
+ boat.vx *= 0.1;
1260
+ boat.vy *= 0.1;
1261
+ }
1262
+ boat.x = (boat.x + this.globalSizeW) % this.globalSizeW;
1263
+ boat.y = (boat.y + this.globalSizeH) % this.globalSizeH;
1264
+ if (Math.abs(boat.vx) > 0.01 || Math.abs(boat.vy) > 0.01) {
1265
+ boat.angle = Math.atan2(boat.vy, boat.vx);
1266
+ }
1267
+ }
1268
+ }
1269
+ }
1270
+ // Paramètres Globaux du "Touilleur" (Tempête/Vortex)
1271
+ setVortexParams(strength, radius) {
1272
+ for (let y = 0; y < this.rows; y++) {
1273
+ for (let x = 0; x < this.cols; x++) {
1274
+ const engine = this.grid.cubes[y][x]?.engine;
1275
+ if (engine) {
1276
+ engine.params.vortexStrength = strength;
1277
+ engine.params.vortexRadius = radius;
1278
+ }
1279
+ }
1280
+ }
1281
+ }
1282
+ /**
1283
+ * Helper to render a specific chunk to an isolated Canvas Context
1284
+ */
1285
+ renderChunk(chunkX, chunkY, ctx, imageData) {
1286
+ const cube = this.grid.cubes[chunkY][chunkX];
1287
+ if (!cube) return;
1288
+ const size = this.chunkSize;
1289
+ const bio = cube.faces[21];
1290
+ const ux = cube.faces[18];
1291
+ const uy = cube.faces[19];
1292
+ const obst = cube.faces[22];
1293
+ const data = imageData.data;
1294
+ for (let i = 0; i < size * size; i++) {
1295
+ const j = i * 4;
1296
+ if (obst[i] > 0.5) {
1297
+ data[j] = 20;
1298
+ data[j + 1] = 20;
1299
+ data[j + 2] = 40;
1300
+ data[j + 3] = 255;
1301
+ } else {
1302
+ const speed = Math.sqrt(ux[i] * ux[i] + uy[i] * uy[i]);
1303
+ const intensity = speed * 1200;
1304
+ data[j] = Math.min(255, bio[i] * 40 + intensity * 0.1);
1305
+ data[j + 1] = Math.min(255, bio[i] * 120 + intensity * 0.4);
1306
+ data[j + 2] = Math.min(255, intensity * 2.5 + 20);
1307
+ data[j + 3] = 255;
1308
+ }
1309
+ }
1310
+ ctx.putImageData(imageData, 0, 0);
1311
+ ctx.fillStyle = "#fff";
1312
+ for (const boat of this.boats) {
1313
+ const localBoatX = boat.x - chunkX * size;
1314
+ const localBoatY = boat.y - chunkY * size;
1315
+ if (localBoatX >= -boat.length && localBoatX <= size + boat.length && localBoatY >= -boat.length && localBoatY <= size + boat.length) {
1316
+ ctx.save();
1317
+ ctx.translate(localBoatX, localBoatY);
1318
+ ctx.rotate(boat.angle);
1319
+ ctx.fillRect(-boat.length / 2, -2, boat.length, 4);
1320
+ ctx.restore();
1321
+ }
1322
+ }
1323
+ }
1324
+ // Interaction globally mapped
1325
+ setInteraction(globalX, globalY, active) {
1326
+ const safeGx = (globalX + this.globalSizeW) % this.globalSizeW;
1327
+ const safeGy = (globalY + this.globalSizeH) % this.globalSizeH;
1328
+ const chunkX = Math.floor(safeGx / this.chunkSize);
1329
+ const chunkY = Math.floor(safeGy / this.chunkSize);
1330
+ const localX = safeGx % this.chunkSize;
1331
+ const localY = safeGy % this.chunkSize;
1332
+ for (let y = 0; y < this.rows; y++) {
1333
+ for (let x = 0; x < this.cols; x++) {
1334
+ const engine = this.grid.cubes[y][x]?.engine;
1335
+ if (engine) engine.interaction.active = false;
1336
+ }
1337
+ }
1338
+ const cube = this.grid.cubes[chunkY][chunkX];
1339
+ if (cube) {
1340
+ const engine = cube.engine;
1341
+ engine.interaction.mouseX = localX;
1342
+ engine.interaction.mouseY = localY;
1343
+ engine.interaction.active = active;
1344
+ }
1345
+ }
1346
+ };
1347
+
1348
+ // src/templates/BlankEngine.ts
1349
+ var BlankEngine = class {
1350
+ get name() {
1351
+ return "My-Blank-Engine";
1352
+ }
1353
+ /**
1354
+ * Called at every simulation tick.
1355
+ * @param faces Array of Float32Array (VRAM Memory mapping of the TriadeCube)
1356
+ * Face[0] is typically the main Output buffer or State A
1357
+ * Face[1] is typically the Secondary buffer or State B (Wait/Swap)
1358
+ * @param mapSize The N x N resolution of the chunk
1359
+ */
1360
+ compute(faces, mapSize) {
1361
+ const length = mapSize * mapSize;
1362
+ if (faces.length < 2) {
1363
+ throw new Error(`[Triade BlankEngine] Insufficient faces. Required 2, but only got ${faces.length}. Ensure your Cube allocation specifies numFaces >= 2.`);
1364
+ }
1365
+ const faceIn = faces[0];
1366
+ const faceOut = faces[1];
1367
+ for (let i = 0; i < length; i++) {
1368
+ const state = faceIn[i];
1369
+ let nextState = state;
1370
+ faceOut[i] = nextState;
1371
+ }
1372
+ faceIn.set(faceOut);
1373
+ }
1374
+ };
1375
+ // Annotate the CommonJS export names for ESM import in node:
1376
+ 0 && (module.exports = {
1377
+ AerodynamicsEngine,
1378
+ BlankEngine,
1379
+ CanvasAdapter,
1380
+ EcosystemEngineO1,
1381
+ GameOfLifeEngine,
1382
+ HeatmapEngine,
1383
+ OceanEngine,
1384
+ OceanSimulatorAddon,
1385
+ OceanWebGLRenderer,
1386
+ OceanWorld,
1387
+ Triade,
1388
+ TriadeCubeV2,
1389
+ TriadeGrid,
1390
+ TriadeMasterBuffer,
1391
+ WebGLAdapter
1392
+ });