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.
@@ -0,0 +1,258 @@
1
+ import { TriadeGrid } from '../../core/TriadeGrid';
2
+
3
+ export class OceanWebGLRenderer {
4
+ private gl: WebGL2RenderingContext;
5
+ private program: WebGLProgram;
6
+ private indexCount: number = 0;
7
+
8
+ private texRho: WebGLTexture;
9
+ private texUx: WebGLTexture;
10
+ private texUy: WebGLTexture;
11
+ private texObst: WebGLTexture;
12
+
13
+ private globalW: number;
14
+ private globalH: number;
15
+ private chunkSize: number;
16
+
17
+ constructor(canvas: HTMLCanvasElement, cols: number, rows: number, chunkSize: number) {
18
+ this.chunkSize = chunkSize;
19
+ this.globalW = cols * chunkSize;
20
+ this.globalH = rows * chunkSize;
21
+
22
+ const gl = canvas.getContext('webgl2');
23
+ if (!gl) throw new Error("WebGL2 non supporté.");
24
+ this.gl = gl;
25
+
26
+ // Extensions pour Float32 Textures
27
+ this.gl.getExtension("EXT_color_buffer_float");
28
+ this.gl.getExtension("OES_texture_float_linear");
29
+
30
+ const vsSource = `#version 300 es
31
+ in vec2 a_position;
32
+ out vec2 v_uv;
33
+ out vec3 v_worldPos;
34
+ uniform sampler2D u_rho; // We sample density in Vertex Shader!
35
+ uniform sampler2D u_obst; // Also sample obstacles in Vertex Shader!
36
+
37
+ void main() {
38
+ v_uv = a_position * 0.5 + 0.5;
39
+ v_uv.y = 1.0 - v_uv.y;
40
+
41
+ // VTF: Vertex Texture Fetch (Real 3D heights from LBM Density)
42
+ float rho = texture(u_rho, v_uv).r;
43
+ float obst = texture(u_obst, v_uv).r;
44
+ float h = 0.0;
45
+ if (obst > 0.5) {
46
+ h = 5.0; // Island elevated plateau
47
+ } else {
48
+ h = (rho - 1.0) * 8.0; // Wave extrusion amplitude (softened)
49
+ }
50
+
51
+ vec3 p = vec3(a_position.x, a_position.y, h);
52
+ v_worldPos = p;
53
+
54
+ // 3D Camera / Perspective Math
55
+ float angleX = -1.1; // Look down tilt
56
+ float cosX = cos(angleX);
57
+ float sinX = sin(angleX);
58
+
59
+ // Apply Pitch rotation
60
+ float yRot = p.y * cosX - p.z * sinX;
61
+ float zRot = p.y * sinX + p.z * cosX;
62
+
63
+ // Move camera back
64
+ zRot -= 2.2;
65
+
66
+ // Perspective projection
67
+ float fov = 1.7;
68
+ gl_Position = vec4(p.x * fov, (yRot - 0.2) * fov, zRot, -zRot);
69
+ }`;
70
+
71
+ const fsSource = `#version 300 es
72
+ precision highp float;
73
+ precision highp sampler2D;
74
+
75
+ in vec2 v_uv;
76
+ in vec3 v_worldPos;
77
+ uniform sampler2D u_rho; // LBM Density -> Heights
78
+ uniform sampler2D u_ux;
79
+ uniform sampler2D u_uy;
80
+ uniform sampler2D u_obst;
81
+
82
+ out vec4 outColor;
83
+
84
+ void main() {
85
+ float obst = texture(u_obst, v_uv).r;
86
+ if (obst > 0.5) {
87
+ // Island material
88
+ vec3 islandTop = vec3(0.5, 0.6, 0.3); // Grass / Forest
89
+ vec3 islandSide = vec3(0.7, 0.6, 0.4); // Sand / Rock
90
+
91
+ // Read local normals to detect cliffs
92
+ float oL = textureOffset(u_obst, v_uv, ivec2(-1, 0)).r;
93
+ float oR = textureOffset(u_obst, v_uv, ivec2(1, 0)).r;
94
+ float oB = textureOffset(u_obst, v_uv, ivec2(0, -1)).r;
95
+ float oT = textureOffset(u_obst, v_uv, ivec2(0, 1)).r;
96
+ float sumO = oL + oR + oB + oT;
97
+
98
+ if (sumO < 3.5) {
99
+ outColor = vec4(islandSide, 1.0); // Cliff / Beach
100
+ } else {
101
+ outColor = vec4(islandTop, 1.0); // Plateau core
102
+ }
103
+ return;
104
+ }
105
+
106
+ // Sample density around the pixel to compute normal vector
107
+ float rL = textureOffset(u_rho, v_uv, ivec2(-1, 0)).r;
108
+ float rR = textureOffset(u_rho, v_uv, ivec2(1, 0)).r;
109
+ float rB = textureOffset(u_rho, v_uv, ivec2(0, -1)).r; // Inverted Y due to texture flip
110
+ float rT = textureOffset(u_rho, v_uv, ivec2(0, 1)).r;
111
+
112
+ // Generate Normal from heightmap (Density is height proxy)
113
+ float strength = 25.0; // Softened normals
114
+ vec3 normal = normalize(vec3((rL - rR) * strength, (rB - rT) * strength, 1.0));
115
+
116
+ // Lighting setup
117
+ vec3 lightDir = normalize(vec3(0.5, 0.8, 0.5)); // Sun angle
118
+ vec3 viewDir = normalize(vec3(0.0, -0.6, 1.0)); // Looking at the water
119
+
120
+ float ux = texture(u_ux, v_uv).r;
121
+ float uy = texture(u_uy, v_uv).r;
122
+ float speed = sqrt(ux*ux + uy*uy);
123
+
124
+ // Water Base Color based on speed (currents)
125
+ vec3 waterDeep = vec3(0.02, 0.2, 0.5);
126
+ vec3 waterShallow = vec3(0.0, 0.6, 0.9);
127
+ vec3 waterColor = mix(waterDeep, waterShallow, clamp(speed * 80.0, 0.0, 1.0));
128
+
129
+ // Diffuse Lighting (Lambert)
130
+ float diff = max(dot(normal, lightDir), 0.0);
131
+ vec3 diffuse = waterColor * (diff * 0.7 + 0.3); // + ambient
132
+
133
+ // Specular Lighting (Phong)
134
+ vec3 reflectDir = reflect(-lightDir, normal);
135
+ float spec = pow(max(dot(viewDir, reflectDir), 0.0), 48.0);
136
+ vec3 specular = vec3(1.0, 1.0, 1.0) * spec * 0.8;
137
+
138
+ // Foam based on density peaks (breaking waves)
139
+ float rho_center = texture(u_rho, v_uv).r;
140
+ float foam = (rho_center > 1.015) ? clamp((rho_center - 1.015) * 15.0, 0.0, 1.0) : 0.0;
141
+
142
+ outColor = vec4(diffuse + specular + foam, 1.0);
143
+ }`;
144
+
145
+ this.program = this.createProgram(vsSource, fsSource);
146
+
147
+ // Generate 256x256 High-Res Grid Mesh for 3D displacement
148
+ const segments = 256;
149
+ const vertices = new Float32Array((segments + 1) * (segments + 1) * 2);
150
+ let ptr = 0;
151
+ for (let y = 0; y <= segments; y++) {
152
+ for (let x = 0; x <= segments; x++) {
153
+ vertices[ptr++] = (x / segments) * 2.0 - 1.0;
154
+ vertices[ptr++] = 1.0 - (y / segments) * 2.0;
155
+ }
156
+ }
157
+
158
+ const indices = new Uint32Array(segments * segments * 6);
159
+ let iPtr = 0;
160
+ for (let y = 0; y < segments; y++) {
161
+ for (let x = 0; x < segments; x++) {
162
+ const i = x + y * (segments + 1);
163
+ indices[iPtr++] = i;
164
+ indices[iPtr++] = i + 1;
165
+ indices[iPtr++] = i + segments + 1;
166
+ indices[iPtr++] = i + segments + 1;
167
+ indices[iPtr++] = i + 1;
168
+ indices[iPtr++] = i + segments + 2;
169
+ }
170
+ }
171
+ this.indexCount = indices.length;
172
+
173
+ const posBuf = this.gl.createBuffer();
174
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, posBuf);
175
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
176
+
177
+ const indexBuf = this.gl.createBuffer()!;
178
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, indexBuf);
179
+ this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, indices, this.gl.STATIC_DRAW);
180
+
181
+ this.texRho = this.createTexture();
182
+ this.texUx = this.createTexture();
183
+ this.texUy = this.createTexture();
184
+ this.texObst = this.createTexture();
185
+
186
+ this.gl.useProgram(this.program);
187
+ this.bindTextureUniform("u_rho", 0);
188
+ this.bindTextureUniform("u_ux", 1);
189
+ this.bindTextureUniform("u_uy", 2);
190
+ this.bindTextureUniform("u_obst", 3);
191
+
192
+ const posLoc = this.gl.getAttribLocation(this.program, "a_position");
193
+ this.gl.enableVertexAttribArray(posLoc);
194
+ this.gl.vertexAttribPointer(posLoc, 2, this.gl.FLOAT, false, 0, 0);
195
+ }
196
+
197
+ private createTexture() {
198
+ const tex = this.gl.createTexture()!;
199
+ this.gl.bindTexture(this.gl.TEXTURE_2D, tex);
200
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); // Linear for smoother visual
201
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
202
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
203
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
204
+
205
+ // Allocate full global Toric map texture
206
+ this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.R32F, this.globalW, this.globalH, 0, this.gl.RED, this.gl.FLOAT, null);
207
+ return tex;
208
+ }
209
+
210
+ private bindTextureUniform(name: string, unit: number) {
211
+ const loc = this.gl.getUniformLocation(this.program, name);
212
+ this.gl.uniform1i(loc, unit);
213
+ }
214
+
215
+ private updateSubTexture(tex: WebGLTexture, unit: number, xOff: number, yOff: number, data: Float32Array) {
216
+ this.gl.activeTexture(this.gl.TEXTURE0 + unit);
217
+ this.gl.bindTexture(this.gl.TEXTURE_2D, tex);
218
+ this.gl.texSubImage2D(this.gl.TEXTURE_2D, 0, xOff, yOff, this.chunkSize, this.chunkSize, this.gl.RED, this.gl.FLOAT, data);
219
+ }
220
+
221
+ public render(grid: TriadeGrid) {
222
+ this.gl.useProgram(this.program);
223
+
224
+ // Upload each chunk into the master textures using O(1) subset uploads
225
+ for (let y = 0; y < grid.rows; y++) {
226
+ for (let x = 0; x < grid.cols; x++) {
227
+ const cube = grid.cubes[y][x];
228
+ if (!cube) continue;
229
+
230
+ const xOff = x * this.chunkSize;
231
+ const yOff = y * this.chunkSize;
232
+
233
+ // Face mapping for OceanEngine (Rho = Face 20)
234
+ this.updateSubTexture(this.texRho, 0, xOff, yOff, cube.faces[20]);
235
+ this.updateSubTexture(this.texUx, 1, xOff, yOff, cube.faces[18]);
236
+ this.updateSubTexture(this.texUy, 2, xOff, yOff, cube.faces[19]);
237
+ this.updateSubTexture(this.texObst, 3, xOff, yOff, cube.faces[22]);
238
+ }
239
+ }
240
+
241
+ // Draw 3D Surface
242
+ this.gl.drawElements(this.gl.TRIANGLES, this.indexCount, this.gl.UNSIGNED_INT, 0);
243
+ }
244
+
245
+ private createProgram(vsSource: string, fsSource: string) {
246
+ const vs = this.gl.createShader(this.gl.VERTEX_SHADER)!;
247
+ this.gl.shaderSource(vs, vsSource);
248
+ this.gl.compileShader(vs);
249
+ const fs = this.gl.createShader(this.gl.FRAGMENT_SHADER)!;
250
+ this.gl.shaderSource(fs, fsSource);
251
+ this.gl.compileShader(fs);
252
+ const prog = this.gl.createProgram()!;
253
+ this.gl.attachShader(prog, vs);
254
+ this.gl.attachShader(prog, fs);
255
+ this.gl.linkProgram(prog);
256
+ return prog;
257
+ }
258
+ }
@@ -0,0 +1,280 @@
1
+ import { TriadeMasterBuffer } from '../../core/TriadeMasterBuffer';
2
+ import { TriadeGrid } from '../../core/TriadeGrid';
3
+ import { OceanEngine } from './OceanEngine';
4
+
5
+ export interface Boat {
6
+ x: number; // Global coordinates
7
+ y: number;
8
+ vx: number;
9
+ vy: number;
10
+ length: number;
11
+ angle: number;
12
+ }
13
+
14
+ export class OceanWorld {
15
+ public readonly cols: number;
16
+ public readonly rows: number;
17
+ public readonly chunkSize: number;
18
+ public readonly globalSizeW: number;
19
+ public readonly globalSizeH: number;
20
+
21
+ public grid: TriadeGrid;
22
+ public boats: Boat[] = [];
23
+ public keys = { up: false, down: false, left: false, right: false };
24
+
25
+ constructor(
26
+ masterBuffer: TriadeMasterBuffer,
27
+ cols: number = 2,
28
+ rows: number = 2,
29
+ chunkSize: number = 64
30
+ ) {
31
+ this.cols = cols;
32
+ this.rows = rows;
33
+ this.chunkSize = chunkSize;
34
+ this.globalSizeW = cols * chunkSize;
35
+ this.globalSizeH = rows * chunkSize;
36
+
37
+ // Create the TriadeGrid with continuous periodic boundaries
38
+ // 24 faces are required by OceanEngine (LBM D2Q9 + Macro + Bio)
39
+ this.grid = new TriadeGrid(cols, rows, chunkSize, masterBuffer, () => new OceanEngine(), 24, true);
40
+
41
+ this.reset();
42
+ }
43
+
44
+ public reset() {
45
+ const w = [4 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, 1 / 36];
46
+
47
+ // Reset distributions and macros
48
+ for (let y = 0; y < this.rows; y++) {
49
+ for (let x = 0; x < this.cols; x++) {
50
+ const cube = this.grid.cubes[y][x]!;
51
+ cube.faces[20].fill(1.0); // rho = 1.0
52
+ cube.faces[18].fill(0.0); // ux = 0
53
+ cube.faces[19].fill(0.0); // uy = 0
54
+ for (let k = 0; k < 9; k++) {
55
+ cube.faces[k].fill(w[k]); // w_k * rho (1.0)
56
+ cube.faces[k + 9].fill(w[k]); // Init swap buffer too!
57
+ }
58
+ }
59
+ }
60
+
61
+ // Initialize Global Obstacles (Central Island)
62
+ const cx = this.globalSizeW / 2;
63
+ const cy = this.globalSizeH / 2;
64
+ for (let y = 0; y < this.rows; y++) {
65
+ for (let x = 0; x < this.cols; x++) {
66
+ const cube = this.grid.cubes[y][x]!;
67
+ const obst = cube.faces[22];
68
+ obst.fill(0.0); // Reset all to water
69
+ for (let ly = 0; ly < this.chunkSize; ly++) {
70
+ for (let lx = 0; lx < this.chunkSize; lx++) {
71
+ const globalX = (x * this.chunkSize + lx);
72
+ const globalY = (y * this.chunkSize + ly);
73
+ const dx = globalX - cx;
74
+ const dy = globalY - cy;
75
+ // Island of radius 20
76
+ if (dx * dx + dy * dy < 400) {
77
+ obst[ly * this.chunkSize + lx] = 1.0;
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ // Reset boats
85
+ for (let b of this.boats) {
86
+ b.x = 20; b.y = 20;
87
+ b.vx = 0; b.vy = 0;
88
+ b.angle = 0;
89
+ }
90
+
91
+ // Force sync once
92
+ 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]);
93
+ }
94
+
95
+ public addBoat(globalX: number, globalY: number, length: number = 20) {
96
+ this.boats.push({ x: globalX, y: globalY, vx: 0, vy: 0, length, angle: 0 });
97
+ }
98
+
99
+ public step() {
100
+ // 1. Math Step for all chunks + Boundary Exchange
101
+ // Synchronize LBM distributions [0..8], Bio data [21], AND Macros [18, 19, 20]
102
+ // Syncing Macros ensures the Boat doesn't read 0 velocity exactly on the boundary cell!
103
+ this.grid.compute([0, 1, 2, 3, 4, 5, 6, 7, 8, 18, 19, 20, 21]);
104
+
105
+ // 2. Global Boat Physics
106
+ for (const boat of this.boats) {
107
+ // Find which chunk the boat is currently in
108
+ const gx = Math.floor(boat.x);
109
+ const gy = Math.floor(boat.y);
110
+
111
+ // Toric world wrap around
112
+ const safeGx = (gx + this.globalSizeW) % this.globalSizeW;
113
+ const safeGy = (gy + this.globalSizeH) % this.globalSizeH;
114
+
115
+ const chunkX = Math.floor(safeGx / this.chunkSize);
116
+ const chunkY = Math.floor(safeGy / this.chunkSize);
117
+ const localX = safeGx % this.chunkSize;
118
+ const localY = safeGy % this.chunkSize;
119
+
120
+ const cube = this.grid.cubes[chunkY][chunkX];
121
+ if (cube) {
122
+ // Read macro velocities from the localized chunk
123
+ const uxFace = cube.faces[18];
124
+ const uyFace = cube.faces[19];
125
+
126
+ const localIdx = localY * this.chunkSize + localX;
127
+ const fux = uxFace[localIdx];
128
+ const fuy = uyFace[localIdx];
129
+
130
+ // --- Player Controls (WASD / ZQSD) ---
131
+ const engineForce = 0.15;
132
+ const turnForce = 0.05;
133
+
134
+ if (this.keys.up) {
135
+ boat.vx += Math.cos(boat.angle) * engineForce;
136
+ boat.vy += Math.sin(boat.angle) * engineForce;
137
+ }
138
+ if (this.keys.down) {
139
+ boat.vx -= Math.cos(boat.angle) * engineForce * 0.5;
140
+ boat.vy -= Math.sin(boat.angle) * engineForce * 0.5;
141
+ }
142
+ if (this.keys.left) boat.angle -= turnForce;
143
+ if (this.keys.right) boat.angle += turnForce;
144
+
145
+ // Si le joueur ne contrôle pas, le moteur tourne au ralenti tout droit
146
+ if (!this.keys.up && !this.keys.down && !this.keys.left && !this.keys.right) {
147
+ boat.vx += Math.cos(boat.angle) * 0.01;
148
+ boat.vy += Math.sin(boat.angle) * 0.01;
149
+ }
150
+
151
+ // Advection: Boat is carried by ocean currents
152
+ boat.vx += fux * 0.25 - boat.vx * 0.05; // Force de l'eau + Friction hydrodynamique
153
+ boat.vy += fuy * 0.25 - boat.vy * 0.05;
154
+
155
+ // Move boat
156
+ boat.x += boat.vx;
157
+ boat.y += boat.vy;
158
+
159
+ // --- HARD LIMIT ON ISLAND COLLISION ---
160
+ // Island radius is approx 20. We stop the boat from entering.
161
+ let cDx = boat.x - this.globalSizeW / 2;
162
+ if (Math.abs(cDx) > this.globalSizeW / 2) cDx = cDx > 0 ? cDx - this.globalSizeW : cDx + this.globalSizeW;
163
+ let cDy = boat.y - this.globalSizeH / 2;
164
+ if (Math.abs(cDy) > this.globalSizeH / 2) cDy = cDy > 0 ? cDy - this.globalSizeH : cDy + this.globalSizeH;
165
+
166
+ const newDist = Math.sqrt(cDx * cDx + cDy * cDy);
167
+ const islandR = 24; // Buffer radius
168
+ if (newDist < islandR) {
169
+ const angleFromIsland = Math.atan2(cDy, cDx);
170
+
171
+ // Repulse strictly to the border of the island
172
+ boat.x += Math.cos(angleFromIsland) * (islandR - newDist);
173
+ boat.y += Math.sin(angleFromIsland) * (islandR - newDist);
174
+
175
+ // Kill velocity
176
+ boat.vx *= 0.1;
177
+ boat.vy *= 0.1;
178
+ }
179
+
180
+ // Toric world wrap for exact coordinates
181
+ boat.x = (boat.x + this.globalSizeW) % this.globalSizeW;
182
+ boat.y = (boat.y + this.globalSizeH) % this.globalSizeH;
183
+
184
+ // Update orientation
185
+ if (Math.abs(boat.vx) > 0.01 || Math.abs(boat.vy) > 0.01) {
186
+ boat.angle = Math.atan2(boat.vy, boat.vx);
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // Paramètres Globaux du "Touilleur" (Tempête/Vortex)
193
+ public setVortexParams(strength: number, radius: number) {
194
+ for (let y = 0; y < this.rows; y++) {
195
+ for (let x = 0; x < this.cols; x++) {
196
+ const engine = this.grid.cubes[y][x]?.engine as OceanEngine;
197
+ if (engine) {
198
+ engine.params.vortexStrength = strength;
199
+ engine.params.vortexRadius = radius;
200
+ }
201
+ }
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Helper to render a specific chunk to an isolated Canvas Context
207
+ */
208
+ public renderChunk(chunkX: number, chunkY: number, ctx: CanvasRenderingContext2D, imageData: ImageData) {
209
+ const cube = this.grid.cubes[chunkY][chunkX];
210
+ if (!cube) return;
211
+
212
+ const size = this.chunkSize;
213
+ const bio = cube.faces[21];
214
+ const ux = cube.faces[18];
215
+ const uy = cube.faces[19];
216
+ const obst = cube.faces[22];
217
+ const data = imageData.data;
218
+
219
+ for (let i = 0; i < size * size; i++) {
220
+ const j = i * 4;
221
+ if (obst[i] > 0.5) {
222
+ data[j] = 20; data[j + 1] = 20; data[j + 2] = 40; data[j + 3] = 255;
223
+ } else {
224
+ const speed = Math.sqrt(ux[i] * ux[i] + uy[i] * uy[i]);
225
+ const intensity = speed * 1200;
226
+ data[j] = Math.min(255, bio[i] * 40 + intensity * 0.1);
227
+ data[j + 1] = Math.min(255, bio[i] * 120 + intensity * 0.4);
228
+ data[j + 2] = Math.min(255, intensity * 2.5 + 20);
229
+ data[j + 3] = 255;
230
+ }
231
+ }
232
+ ctx.putImageData(imageData, 0, 0);
233
+
234
+ // Draw boats that belong to THIS chunk
235
+ ctx.fillStyle = "#fff";
236
+ for (const boat of this.boats) {
237
+ // Check if boat is in this chunk visually
238
+ // (We convert global boat coords to local chunk coords to draw them relative to this Canvas)
239
+ const localBoatX = boat.x - chunkX * size;
240
+ const localBoatY = boat.y - chunkY * size;
241
+
242
+ // Simple culling (draw if it's within chunk bounds or slightly outside to handle edges)
243
+ if (localBoatX >= -boat.length && localBoatX <= size + boat.length &&
244
+ localBoatY >= -boat.length && localBoatY <= size + boat.length) {
245
+ ctx.save();
246
+ ctx.translate(localBoatX, localBoatY);
247
+ ctx.rotate(boat.angle);
248
+ ctx.fillRect(-boat.length / 2, -2, boat.length, 4);
249
+ ctx.restore();
250
+ }
251
+ }
252
+ }
253
+
254
+ // Interaction globally mapped
255
+ public setInteraction(globalX: number, globalY: number, active: boolean) {
256
+ const safeGx = (globalX + this.globalSizeW) % this.globalSizeW;
257
+ const safeGy = (globalY + this.globalSizeH) % this.globalSizeH;
258
+
259
+ const chunkX = Math.floor(safeGx / this.chunkSize);
260
+ const chunkY = Math.floor(safeGy / this.chunkSize);
261
+ const localX = safeGx % this.chunkSize;
262
+ const localY = safeGy % this.chunkSize;
263
+
264
+ // Reset all chunks interactions first to avoid "sticky" mouse
265
+ for (let y = 0; y < this.rows; y++) {
266
+ for (let x = 0; x < this.cols; x++) {
267
+ const engine = this.grid.cubes[y][x]?.engine as OceanEngine;
268
+ if (engine) engine.interaction.active = false;
269
+ }
270
+ }
271
+
272
+ const cube = this.grid.cubes[chunkY][chunkX];
273
+ if (cube) {
274
+ const engine = cube.engine as OceanEngine;
275
+ engine.interaction.mouseX = localX;
276
+ engine.interaction.mouseY = localY;
277
+ engine.interaction.active = active;
278
+ }
279
+ }
280
+ }
@@ -0,0 +1,58 @@
1
+ import { TriadeMasterBuffer } from './TriadeMasterBuffer';
2
+ import type { ITriadeEngine } from '../engines/ITriadeEngine';
3
+
4
+ export class TriadeCubeV2 {
5
+ public readonly mapSize: number;
6
+ public readonly faces: Float32Array[] = [];
7
+ public readonly offset: number; // Added this property
8
+ public engine: ITriadeEngine | null = null;
9
+
10
+ /**
11
+ * @param mapSize La résolution (N x N)
12
+ * @param masterBuffer L'allocateur de RAM central de l'Orchestrateur
13
+ */
14
+ constructor(mapSize: number, masterBuffer: TriadeMasterBuffer, numFaces: number = 6) {
15
+ this.mapSize = mapSize;
16
+
17
+ // 1. Demander la réservation de l'espace sur la bande mémoire (O(1) Memory Layout)
18
+ this.offset = masterBuffer.allocateCube(mapSize, numFaces);
19
+
20
+ // 2. Création des Vues
21
+ const floatCount = mapSize * mapSize;
22
+ const bytesPerFace = floatCount * Float32Array.BYTES_PER_ELEMENT;
23
+
24
+ // Génération des N "Views" mathématiques
25
+ for (let i = 0; i < numFaces; i++) {
26
+ this.faces.push(
27
+ new Float32Array(
28
+ masterBuffer.buffer,
29
+ this.offset + (i * bytesPerFace),
30
+ floatCount
31
+ )
32
+ );
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Injecte le cerveau mathématique dans ce paquet mémoire.
38
+ */
39
+ setEngine(engine: ITriadeEngine) {
40
+ this.engine = engine;
41
+ }
42
+
43
+ /**
44
+ * Exécute le calcul de la Frame (O1, Wavefront, Automates)
45
+ */
46
+ compute() {
47
+ if (!this.engine) {
48
+ console.warn("[TriadeCubeV2] Aucun Engine (Cerveau) n'a été assigné à ce cube.");
49
+ return;
50
+ }
51
+ this.engine.compute(this.faces, this.mapSize);
52
+ }
53
+
54
+ /** Helper pour vider une face spécifique */
55
+ clearFace(faceIndex: number) {
56
+ this.faces[faceIndex].fill(0);
57
+ }
58
+ }