@woosh/meep-engine 2.157.0 → 2.159.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.
Files changed (71) hide show
  1. package/package.json +1 -1
  2. package/src/core/binary/BitSet.d.ts.map +1 -1
  3. package/src/core/binary/BitSet.js +6 -1
  4. package/src/core/binary/float_to_uint8.d.ts +4 -1
  5. package/src/core/binary/float_to_uint8.d.ts.map +1 -1
  6. package/src/core/binary/float_to_uint8.js +7 -2
  7. package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.d.ts +11 -1
  8. package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.d.ts.map +1 -1
  9. package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +37 -14
  10. package/src/core/geom/3d/shape/PosedShape3D.d.ts +17 -0
  11. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  12. package/src/core/geom/3d/shape/PosedShape3D.js +50 -0
  13. package/src/core/math/gaussian.d.ts +7 -6
  14. package/src/core/math/gaussian.d.ts.map +1 -1
  15. package/src/core/math/gaussian.js +9 -8
  16. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
  17. package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
  18. package/src/engine/graphics/ecs/trail2d/Trail2DFlags.d.ts +1 -0
  19. package/src/engine/graphics/ecs/trail2d/Trail2DFlags.js +9 -1
  20. package/src/engine/physics/fluid/FluidField.d.ts +53 -9
  21. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  22. package/src/engine/physics/fluid/FluidField.js +684 -600
  23. package/src/engine/physics/fluid/FluidSimulator.d.ts +53 -38
  24. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  25. package/src/engine/physics/fluid/FluidSimulator.js +252 -178
  26. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +155 -26
  27. package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts +72 -0
  28. package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts.map +1 -0
  29. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -0
  30. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +117 -0
  31. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -0
  32. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -0
  33. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  34. package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts +62 -12
  35. package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts.map +1 -1
  36. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +135 -38
  37. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts.map +1 -1
  38. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +85 -38
  39. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
  40. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +104 -50
  41. package/src/engine/physics/fluid/prototype.js +25 -1
  42. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.d.ts +30 -0
  43. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.d.ts.map +1 -0
  44. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -0
  45. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts +42 -0
  46. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts.map +1 -0
  47. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +319 -0
  48. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.d.ts +53 -0
  49. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.d.ts.map +1 -0
  50. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -0
  51. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.d.ts +46 -0
  52. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.d.ts.map +1 -0
  53. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -0
  54. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts +40 -0
  55. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts.map +1 -0
  56. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +165 -0
  57. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.d.ts +44 -0
  58. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.d.ts.map +1 -0
  59. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -0
  60. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.d.ts +38 -0
  61. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.d.ts.map +1 -0
  62. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -0
  63. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.d.ts +52 -0
  64. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.d.ts.map +1 -0
  65. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -0
  66. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.d.ts +38 -0
  67. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.d.ts.map +1 -0
  68. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -0
  69. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts +0 -41
  70. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts.map +0 -1
  71. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.js +0 -124
@@ -0,0 +1,165 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+
3
+ /**
4
+ * Vorticity confinement (Fedkiw, Stam, Jensen 2001) for a MAC velocity field.
5
+ *
6
+ * Three passes:
7
+ *
8
+ * 1. average the face velocities to cell centers (exact on the staggered
9
+ * layout: two faces per axis),
10
+ * 2. ω = ∇×v and |ω| at centers by central differences (interior cells;
11
+ * the boundary ring and solid cells get 0),
12
+ * 3. the confinement force f = ε·(N × ω), N = ∇|ω|/|∇|ω||, is computed per
13
+ * cell and scattered to the cell's six faces at half weight each —
14
+ * interior faces accumulate halves from both neighbours, recovering the
15
+ * face-centered average of the cell forces. Pinned faces (adjacent
16
+ * solid) receive nothing.
17
+ *
18
+ * See {@link v3_grid_apply_vorticity_confinement} for the scheme rationale
19
+ * and the energy note (confinement ADDS energy; pair with damping).
20
+ *
21
+ * @param {Float32Array} vel_u Mutated in place, (res_x+1)·res_y·res_z.
22
+ * @param {Float32Array} vel_v Mutated in place.
23
+ * @param {Float32Array} vel_w Mutated in place.
24
+ * @param {Uint8Array} face_solid_x Pinned-face masks.
25
+ * @param {Uint8Array} face_solid_y
26
+ * @param {Uint8Array} face_solid_z
27
+ * @param {Float32Array} scratch_cu Cell-count working buffers (callers may
28
+ * pass larger, e.g. face-sized, arrays). All mutated.
29
+ * @param {Float32Array} scratch_cv
30
+ * @param {Float32Array} scratch_cw
31
+ * @param {Float32Array} scratch_wx
32
+ * @param {Float32Array} scratch_wy
33
+ * @param {Float32Array} scratch_wz
34
+ * @param {Float32Array} scratch_wmag
35
+ * @param {number} res_x
36
+ * @param {number} res_y
37
+ * @param {number} res_z
38
+ * @param {number} epsilon_dt Confinement strength ε times the timestep.
39
+ * @param {Uint8Array} solid
40
+ */
41
+ export function v3_mac_apply_vorticity_confinement(
42
+ vel_u, vel_v, vel_w,
43
+ face_solid_x, face_solid_y, face_solid_z,
44
+ scratch_cu, scratch_cv, scratch_cw,
45
+ scratch_wx, scratch_wy, scratch_wz, scratch_wmag,
46
+ res_x, res_y, res_z,
47
+ epsilon_dt,
48
+ solid
49
+ ) {
50
+ const cell_count = res_x * res_y * res_z;
51
+ assert.greaterThanOrEqual(scratch_cu.length, cell_count, "scratch_cu covers grid");
52
+ assert.greaterThanOrEqual(scratch_wmag.length, cell_count, "scratch_wmag covers grid");
53
+ assert.isNumber(epsilon_dt, "epsilon_dt");
54
+ assert.greaterThanOrEqual(epsilon_dt, 0, "epsilon_dt");
55
+
56
+ if (epsilon_dt === 0) {
57
+ return;
58
+ }
59
+
60
+ const cell_slice = res_x * res_y;
61
+ const sx = res_x + 1;
62
+ const v_slice = res_x * (res_y + 1);
63
+ const last_x = res_x - 1;
64
+ const last_y = res_y - 1;
65
+ const last_z = res_z - 1;
66
+
67
+ // ─── pass 1: face → center averages ─────────────────────────────────────
68
+ for (let z = 0; z < res_z; z++) {
69
+ const c_z = z * cell_slice;
70
+ const u_z = z * sx * res_y;
71
+ const v_z = z * v_slice;
72
+ for (let y = 0; y < res_y; y++) {
73
+ const c_y = c_z + y * res_x;
74
+ const u_y = u_z + y * sx;
75
+ const v_lo = v_z + y * res_x;
76
+ const w_lo = c_z + y * res_x;
77
+ for (let x = 0; x < res_x; x++) {
78
+ const c = c_y + x;
79
+ if (solid[c] !== 0) {
80
+ scratch_cu[c] = 0;
81
+ scratch_cv[c] = 0;
82
+ scratch_cw[c] = 0;
83
+ continue;
84
+ }
85
+ scratch_cu[c] = 0.5 * (vel_u[u_y + x] + vel_u[u_y + x + 1]);
86
+ scratch_cv[c] = 0.5 * (vel_v[v_lo + x] + vel_v[v_lo + res_x + x]);
87
+ scratch_cw[c] = 0.5 * (vel_w[w_lo + x] + vel_w[w_lo + cell_slice + x]);
88
+ }
89
+ }
90
+ }
91
+
92
+ // ─── pass 2: ω = ∇×v and |ω| at centers ─────────────────────────────────
93
+ scratch_wx.fill(0, 0, cell_count);
94
+ scratch_wy.fill(0, 0, cell_count);
95
+ scratch_wz.fill(0, 0, cell_count);
96
+ scratch_wmag.fill(0, 0, cell_count);
97
+
98
+ for (let z = 1; z < last_z; z++) {
99
+ const c_z = z * cell_slice;
100
+ for (let y = 1; y < last_y; y++) {
101
+ const c_y = c_z + y * res_x;
102
+ for (let x = 1; x < last_x; x++) {
103
+ const c = c_y + x;
104
+ if (solid[c] !== 0) {
105
+ continue;
106
+ }
107
+ const wx = 0.5 * ((scratch_cw[c + res_x] - scratch_cw[c - res_x]) - (scratch_cv[c + cell_slice] - scratch_cv[c - cell_slice]));
108
+ const wy = 0.5 * ((scratch_cu[c + cell_slice] - scratch_cu[c - cell_slice]) - (scratch_cw[c + 1] - scratch_cw[c - 1]));
109
+ const wz = 0.5 * ((scratch_cv[c + 1] - scratch_cv[c - 1]) - (scratch_cu[c + res_x] - scratch_cu[c - res_x]));
110
+ scratch_wx[c] = wx;
111
+ scratch_wy[c] = wy;
112
+ scratch_wz[c] = wz;
113
+ scratch_wmag[c] = Math.sqrt(wx * wx + wy * wy + wz * wz);
114
+ }
115
+ }
116
+ }
117
+
118
+ // ─── pass 3: f = ε·(N × ω) per cell, scattered to faces ─────────────────
119
+ const magnitude_floor = 1e-10;
120
+
121
+ for (let z = 1; z < last_z; z++) {
122
+ const c_z = z * cell_slice;
123
+ const u_z = z * sx * res_y;
124
+ const v_z = z * v_slice;
125
+ for (let y = 1; y < last_y; y++) {
126
+ const c_y = c_z + y * res_x;
127
+ const u_y = u_z + y * sx;
128
+ const v_lo = v_z + y * res_x;
129
+ const w_lo = c_z + y * res_x;
130
+ for (let x = 1; x < last_x; x++) {
131
+ const c = c_y + x;
132
+ if (solid[c] !== 0) {
133
+ continue;
134
+ }
135
+
136
+ const gx = 0.5 * (scratch_wmag[c + 1] - scratch_wmag[c - 1]);
137
+ const gy = 0.5 * (scratch_wmag[c + res_x] - scratch_wmag[c - res_x]);
138
+ const gz = 0.5 * (scratch_wmag[c + cell_slice] - scratch_wmag[c - cell_slice]);
139
+
140
+ const g_len = Math.sqrt(gx * gx + gy * gy + gz * gz);
141
+ if (g_len < magnitude_floor) {
142
+ continue;
143
+ }
144
+ const s = epsilon_dt / g_len;
145
+
146
+ const wx = scratch_wx[c];
147
+ const wy = scratch_wy[c];
148
+ const wz = scratch_wz[c];
149
+ const fx = 0.5 * s * (gy * wz - gz * wy);
150
+ const fy = 0.5 * s * (gz * wx - gx * wz);
151
+ const fz = 0.5 * s * (gx * wy - gy * wx);
152
+
153
+ const u_l = u_y + x;
154
+ if (face_solid_x[u_l] === 0) vel_u[u_l] += fx;
155
+ if (face_solid_x[u_l + 1] === 0) vel_u[u_l + 1] += fx;
156
+ const v_b = v_lo + x;
157
+ if (face_solid_y[v_b] === 0) vel_v[v_b] += fy;
158
+ if (face_solid_y[v_b + res_x] === 0) vel_v[v_b + res_x] += fy;
159
+ const w_n = w_lo + x;
160
+ if (face_solid_z[w_n] === 0) vel_w[w_n] += fz;
161
+ if (face_solid_z[w_n + cell_slice] === 0) vel_w[w_n + cell_slice] += fz;
162
+ }
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Clip a semi-Lagrangian trace segment against solid cells.
3
+ *
4
+ * Back-traces (and MacCormack's forward probes) are straight segments from a
5
+ * known-fluid start position toward an end position computed from the carrier
6
+ * velocity. When the end lands inside a solid cell, sampling there reads
7
+ * frozen in-wall values — the source of scalar bleed through walls and of
8
+ * velocity tunnelling into disconnected pockets. This helper walks the
9
+ * segment back by bisection until the end point is in fluid again.
10
+ *
11
+ * Positions are in CELL-CENTER coordinates: cell `(i, j, k)` spans
12
+ * `[i−0.5, i+0.5)` per axis, so the cell containing a point is `round(p)`,
13
+ * clamped to the grid (consistent with the samplers' clamped reads —
14
+ * out-of-grid points belong to the nearest edge cell).
15
+ *
16
+ * Four bisection iterations resolve the boundary to 1/16 of the segment —
17
+ * sub-cell precision at production CFL (segments span ≲ 1.5 cells). A
18
+ * re-entrant segment (exits fluid, crosses a thin wall, re-enters) clips at
19
+ * one of its boundaries rather than the first — acceptable at these segment
20
+ * lengths, and strictly better than sampling inside the wall.
21
+ *
22
+ * The result is written into `out[0..2]`. If the end point is already in
23
+ * fluid the end coordinates are written unchanged.
24
+ *
25
+ * PERFORMANCE CONTRACT: this function contains a loop, which keeps V8 from
26
+ * inlining it — a per-face call costs several times the entire surrounding
27
+ * advection body (measured 8.6× on the SL kernel). Hot kernels must inline
28
+ * the "is the end cell fluid?" test themselves (the `(p + 0.5) | 0` + clamp
29
+ * + one byte load below) and call this helper only on the rare wall hit.
30
+ *
31
+ * @param {Float32Array|number[]} out length-3 destination for the clipped end.
32
+ * @param {Uint8Array} solid Cell-centered solid flags.
33
+ * @param {number} res_x
34
+ * @param {number} res_y
35
+ * @param {number} res_z
36
+ * @param {number} start_x Known-fluid segment start (cell-center coords).
37
+ * @param {number} start_y
38
+ * @param {number} start_z
39
+ * @param {number} end_x Desired segment end.
40
+ * @param {number} end_y
41
+ * @param {number} end_z
42
+ */
43
+ export function v3_mac_clip_trace(out: Float32Array | number[], solid: Uint8Array, res_x: number, res_y: number, res_z: number, start_x: number, start_y: number, start_z: number, end_x: number, end_y: number, end_z: number): void;
44
+ //# sourceMappingURL=v3_mac_clip_trace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_clip_trace.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_clip_trace.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,uCAZW,YAAY,GAAC,MAAM,EAAE,SACrB,UAAU,SACV,MAAM,SACN,MAAM,SACN,MAAM,WACN,MAAM,WACN,MAAM,WACN,MAAM,SACN,MAAM,SACN,MAAM,SACN,MAAM,QA4BhB"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Clip a semi-Lagrangian trace segment against solid cells.
3
+ *
4
+ * Back-traces (and MacCormack's forward probes) are straight segments from a
5
+ * known-fluid start position toward an end position computed from the carrier
6
+ * velocity. When the end lands inside a solid cell, sampling there reads
7
+ * frozen in-wall values — the source of scalar bleed through walls and of
8
+ * velocity tunnelling into disconnected pockets. This helper walks the
9
+ * segment back by bisection until the end point is in fluid again.
10
+ *
11
+ * Positions are in CELL-CENTER coordinates: cell `(i, j, k)` spans
12
+ * `[i−0.5, i+0.5)` per axis, so the cell containing a point is `round(p)`,
13
+ * clamped to the grid (consistent with the samplers' clamped reads —
14
+ * out-of-grid points belong to the nearest edge cell).
15
+ *
16
+ * Four bisection iterations resolve the boundary to 1/16 of the segment —
17
+ * sub-cell precision at production CFL (segments span ≲ 1.5 cells). A
18
+ * re-entrant segment (exits fluid, crosses a thin wall, re-enters) clips at
19
+ * one of its boundaries rather than the first — acceptable at these segment
20
+ * lengths, and strictly better than sampling inside the wall.
21
+ *
22
+ * The result is written into `out[0..2]`. If the end point is already in
23
+ * fluid the end coordinates are written unchanged.
24
+ *
25
+ * PERFORMANCE CONTRACT: this function contains a loop, which keeps V8 from
26
+ * inlining it — a per-face call costs several times the entire surrounding
27
+ * advection body (measured 8.6× on the SL kernel). Hot kernels must inline
28
+ * the "is the end cell fluid?" test themselves (the `(p + 0.5) | 0` + clamp
29
+ * + one byte load below) and call this helper only on the rare wall hit.
30
+ *
31
+ * @param {Float32Array|number[]} out length-3 destination for the clipped end.
32
+ * @param {Uint8Array} solid Cell-centered solid flags.
33
+ * @param {number} res_x
34
+ * @param {number} res_y
35
+ * @param {number} res_z
36
+ * @param {number} start_x Known-fluid segment start (cell-center coords).
37
+ * @param {number} start_y
38
+ * @param {number} start_z
39
+ * @param {number} end_x Desired segment end.
40
+ * @param {number} end_y
41
+ * @param {number} end_z
42
+ */
43
+ export function v3_mac_clip_trace(out, solid, res_x, res_y, res_z, start_x, start_y, start_z, end_x, end_y, end_z) {
44
+ if (!point_in_solid(solid, res_x, res_y, res_z, end_x, end_y, end_z)) {
45
+ out[0] = end_x;
46
+ out[1] = end_y;
47
+ out[2] = end_z;
48
+ return;
49
+ }
50
+
51
+ // Bisect t ∈ [t_fluid, t_solid] on p(t) = start + t·(end − start).
52
+ let t_fluid = 0;
53
+ let t_solid = 1;
54
+ for (let i = 0; i < 4; i++) {
55
+ const t = 0.5 * (t_fluid + t_solid);
56
+ const px = start_x + t * (end_x - start_x);
57
+ const py = start_y + t * (end_y - start_y);
58
+ const pz = start_z + t * (end_z - start_z);
59
+ if (point_in_solid(solid, res_x, res_y, res_z, px, py, pz)) {
60
+ t_solid = t;
61
+ } else {
62
+ t_fluid = t;
63
+ }
64
+ }
65
+
66
+ out[0] = start_x + t_fluid * (end_x - start_x);
67
+ out[1] = start_y + t_fluid * (end_y - start_y);
68
+ out[2] = start_z + t_fluid * (end_z - start_z);
69
+ }
70
+
71
+ /**
72
+ * Whether the cell containing the point (clamped to the grid) is solid.
73
+ *
74
+ * @param {Uint8Array} solid
75
+ * @param {number} res_x
76
+ * @param {number} res_y
77
+ * @param {number} res_z
78
+ * @param {number} px cell-center coordinates
79
+ * @param {number} py
80
+ * @param {number} pz
81
+ * @return {boolean}
82
+ */
83
+ function point_in_solid(solid, res_x, res_y, res_z, px, py, pz) {
84
+ // (p + 0.5) | 0 is round-half-up for p ≥ −0.5 and truncates toward zero
85
+ // below that — the subsequent clamp makes both behaviours land on the
86
+ // same edge cell, so the result matches Math.round + clamp everywhere
87
+ // it matters, at a fraction of the cost.
88
+ let x = (px + 0.5) | 0;
89
+ let y = (py + 0.5) | 0;
90
+ let z = (pz + 0.5) | 0;
91
+ if (x < 0) x = 0; else if (x >= res_x) x = res_x - 1;
92
+ if (y < 0) y = 0; else if (y >= res_y) y = res_y - 1;
93
+ if (z < 0) z = 0; else if (z >= res_z) z = res_z - 1;
94
+ return solid[z * res_x * res_y + y * res_x + x] !== 0;
95
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Divergence of a MAC (staggered, face-centered) velocity field into a
3
+ * cell-centered scalar field:
4
+ *
5
+ * div(c) = (u[x+1] − u[x]) + (v[y+1] − v[y]) + (w[z+1] − w[z]) (h = 1)
6
+ *
7
+ * The defining advantage over the collocated central-difference form: this
8
+ * divergence is the EXACT adjoint of {@link v3_mac_subtract_pressure_gradient}
9
+ * composed with the 7-point Laplacian in {@link v3_grid_solve_pressure} —
10
+ * an exact pressure solve drives this divergence to exactly zero. No
11
+ * operator-mismatch floor, no projection-invisible checkerboard mode, no
12
+ * boundary special cases (boundary faces exist and carry real outflow).
13
+ *
14
+ * PINNED faces (either adjacent cell solid) are read at face value — their
15
+ * stored velocity IS the wall's normal velocity (see
16
+ * {@link v3_mac_compute_face_solid}): zero for static solids, the wall's
17
+ * speed for moving ones. A moving wall therefore appears here as divergence
18
+ * in the adjacent fluid cells, and the pressure solve turns that into the
19
+ * push/suction the wall exerts — the standard MAC moving-boundary mechanism.
20
+ * Stray writes into pinned faces cannot masquerade as wall motion: the face
21
+ * mask rebuild zeroes velocities on every unpinned→pinned transition, and
22
+ * all transport/effector kernels skip pinned faces.
23
+ *
24
+ * Solid cells get divergence 0 — they are not degrees of freedom.
25
+ *
26
+ * Face grid layout: see {@link v3_mac_compute_face_solid}.
27
+ *
28
+ * @param {Float32Array} divergence Destination, cell-centered. Mutated.
29
+ * @param {Float32Array} vel_u x-face velocities, (res_x+1)·res_y·res_z.
30
+ * @param {Float32Array} vel_v y-face velocities, res_x·(res_y+1)·res_z.
31
+ * @param {Float32Array} vel_w z-face velocities, res_x·res_y·(res_z+1).
32
+ * @param {number} res_x
33
+ * @param {number} res_y
34
+ * @param {number} res_z
35
+ * @param {Uint8Array} solid Cell-centered solid flags.
36
+ */
37
+ export function v3_mac_compute_divergence(divergence: Float32Array, vel_u: Float32Array, vel_v: Float32Array, vel_w: Float32Array, res_x: number, res_y: number, res_z: number, solid: Uint8Array): void;
38
+ //# sourceMappingURL=v3_mac_compute_divergence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_compute_divergence.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_compute_divergence.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,sDATW,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,MAAM,SACN,MAAM,SACN,MAAM,SACN,UAAU,QAwCpB"}
@@ -0,0 +1,77 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+
3
+ /**
4
+ * Divergence of a MAC (staggered, face-centered) velocity field into a
5
+ * cell-centered scalar field:
6
+ *
7
+ * div(c) = (u[x+1] − u[x]) + (v[y+1] − v[y]) + (w[z+1] − w[z]) (h = 1)
8
+ *
9
+ * The defining advantage over the collocated central-difference form: this
10
+ * divergence is the EXACT adjoint of {@link v3_mac_subtract_pressure_gradient}
11
+ * composed with the 7-point Laplacian in {@link v3_grid_solve_pressure} —
12
+ * an exact pressure solve drives this divergence to exactly zero. No
13
+ * operator-mismatch floor, no projection-invisible checkerboard mode, no
14
+ * boundary special cases (boundary faces exist and carry real outflow).
15
+ *
16
+ * PINNED faces (either adjacent cell solid) are read at face value — their
17
+ * stored velocity IS the wall's normal velocity (see
18
+ * {@link v3_mac_compute_face_solid}): zero for static solids, the wall's
19
+ * speed for moving ones. A moving wall therefore appears here as divergence
20
+ * in the adjacent fluid cells, and the pressure solve turns that into the
21
+ * push/suction the wall exerts — the standard MAC moving-boundary mechanism.
22
+ * Stray writes into pinned faces cannot masquerade as wall motion: the face
23
+ * mask rebuild zeroes velocities on every unpinned→pinned transition, and
24
+ * all transport/effector kernels skip pinned faces.
25
+ *
26
+ * Solid cells get divergence 0 — they are not degrees of freedom.
27
+ *
28
+ * Face grid layout: see {@link v3_mac_compute_face_solid}.
29
+ *
30
+ * @param {Float32Array} divergence Destination, cell-centered. Mutated.
31
+ * @param {Float32Array} vel_u x-face velocities, (res_x+1)·res_y·res_z.
32
+ * @param {Float32Array} vel_v y-face velocities, res_x·(res_y+1)·res_z.
33
+ * @param {Float32Array} vel_w z-face velocities, res_x·res_y·(res_z+1).
34
+ * @param {number} res_x
35
+ * @param {number} res_y
36
+ * @param {number} res_z
37
+ * @param {Uint8Array} solid Cell-centered solid flags.
38
+ */
39
+ export function v3_mac_compute_divergence(divergence, vel_u, vel_v, vel_w, res_x, res_y, res_z, solid) {
40
+ const cell_count = res_x * res_y * res_z;
41
+ assert.greaterThanOrEqual(divergence.length, cell_count, "divergence covers grid");
42
+ assert.greaterThanOrEqual(vel_u.length, (res_x + 1) * res_y * res_z, "vel_u covers grid");
43
+ assert.greaterThanOrEqual(vel_v.length, res_x * (res_y + 1) * res_z, "vel_v covers grid");
44
+ assert.greaterThanOrEqual(vel_w.length, res_x * res_y * (res_z + 1), "vel_w covers grid");
45
+ assert.greaterThanOrEqual(solid.length, cell_count, "solid covers grid");
46
+
47
+ const cell_slice = res_x * res_y;
48
+ const sx = res_x + 1;
49
+
50
+ for (let z = 0; z < res_z; z++) {
51
+ const c_z = z * cell_slice;
52
+ const u_z = z * sx * res_y;
53
+ const v_z = z * res_x * (res_y + 1);
54
+
55
+ for (let y = 0; y < res_y; y++) {
56
+ const c_y = c_z + y * res_x;
57
+ const u_y = u_z + y * sx;
58
+ const v_lo = v_z + y * res_x;
59
+ const v_hi = v_lo + res_x;
60
+ const w_lo = c_z + y * res_x; // z-face slice size == cell slice
61
+ const w_hi = w_lo + cell_slice;
62
+
63
+ for (let x = 0; x < res_x; x++) {
64
+ const c = c_y + x;
65
+
66
+ if (solid[c] !== 0) {
67
+ divergence[c] = 0;
68
+ continue;
69
+ }
70
+
71
+ divergence[c] = (vel_u[u_y + x + 1] - vel_u[u_y + x])
72
+ + (vel_v[v_hi + x] - vel_v[v_lo + x])
73
+ + (vel_w[w_hi + x] - vel_w[w_lo + x]);
74
+ }
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Bake the per-face "pinned" masks for a MAC (staggered) velocity layout,
3
+ * zeroing the velocity of faces that just BECAME pinned.
4
+ *
5
+ * A face is PINNED (`1`) when either adjacent cell is solid: its normal
6
+ * velocity is a BOUNDARY CONDITION, not a degree of freedom. The stored value
7
+ * of a pinned face is the wall's normal velocity — `0` for static solids,
8
+ * non-zero for moving ones ({@link FluidObstacleSystem} writes those). Every
9
+ * fluid kernel honours the contract: the divergence reads pinned values (a
10
+ * moving wall pushes fluid), the pressure-gradient subtraction and advection
11
+ * leave them untouched, effectors and damping skip them.
12
+ *
13
+ * Transition rules, applied per face as the masks are rebuilt:
14
+ *
15
+ * - unpinned → pinned: the face velocity is ZEROED — the static-wall
16
+ * default, and the guard that keeps stale fluid velocity from
17
+ * masquerading as wall motion. A solid author who wants a moving wall
18
+ * writes the face velocity AFTER this recompute (the obstacle system
19
+ * does exactly that).
20
+ * - pinned → pinned: the value is PRESERVED — boundary conditions written
21
+ * by the solid's owner survive subsequent recomputes.
22
+ * - pinned → unpinned: the value is preserved too — fluid uncovered by a
23
+ * retreating wall inherits the wall's velocity, which is the physically
24
+ * sensible seed (and free wake generation).
25
+ *
26
+ * Domain-edge faces (between a cell and the outside) are NOT pinned — the
27
+ * domain boundary is open (ghost pressure 0; see
28
+ * {@link v3_grid_compute_solid_neighbour_mask}) and flow vents through it.
29
+ *
30
+ * Face grids and their indexing:
31
+ *
32
+ * face_x : (res_x+1) × res_y × res_z, index z·(res_x+1)·res_y + y·(res_x+1) + x
33
+ * face (x, y, z) sits between cells (x−1, y, z) and (x, y, z)
34
+ * face_y : res_x × (res_y+1) × res_z, index z·res_x·(res_y+1) + y·res_x + x
35
+ * face_z : res_x × res_y × (res_z+1), index z·res_x·res_y + y·res_x + x
36
+ *
37
+ * O(faces) — two byte-reads per interior face plus the in-place transition
38
+ * comparison. Mask outputs are fully replaced.
39
+ *
40
+ * @param {Uint8Array} face_x Pin mask, length ≥ (res_x+1)·res_y·res_z. Mutated.
41
+ * @param {Uint8Array} face_y Pin mask, length ≥ res_x·(res_y+1)·res_z. Mutated.
42
+ * @param {Uint8Array} face_z Pin mask, length ≥ res_x·res_y·(res_z+1). Mutated.
43
+ * @param {Float32Array} vel_u x-face velocities — zeroed on 0→1 pin transition.
44
+ * @param {Float32Array} vel_v y-face velocities.
45
+ * @param {Float32Array} vel_w z-face velocities.
46
+ * @param {Uint8Array} solid Cell-centered solid flags. Non-zero = solid.
47
+ * @param {number} res_x
48
+ * @param {number} res_y
49
+ * @param {number} res_z
50
+ */
51
+ export function v3_mac_compute_face_solid(face_x: Uint8Array, face_y: Uint8Array, face_z: Uint8Array, vel_u: Float32Array, vel_v: Float32Array, vel_w: Float32Array, solid: Uint8Array, res_x: number, res_y: number, res_z: number): void;
52
+ //# sourceMappingURL=v3_mac_compute_face_solid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_compute_face_solid.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AACH,kDAXW,UAAU,UACV,UAAU,UACV,UAAU,SACV,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,UAAU,SACV,MAAM,SACN,MAAM,SACN,MAAM,QAgFhB"}
@@ -0,0 +1,131 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+
3
+ /**
4
+ * Bake the per-face "pinned" masks for a MAC (staggered) velocity layout,
5
+ * zeroing the velocity of faces that just BECAME pinned.
6
+ *
7
+ * A face is PINNED (`1`) when either adjacent cell is solid: its normal
8
+ * velocity is a BOUNDARY CONDITION, not a degree of freedom. The stored value
9
+ * of a pinned face is the wall's normal velocity — `0` for static solids,
10
+ * non-zero for moving ones ({@link FluidObstacleSystem} writes those). Every
11
+ * fluid kernel honours the contract: the divergence reads pinned values (a
12
+ * moving wall pushes fluid), the pressure-gradient subtraction and advection
13
+ * leave them untouched, effectors and damping skip them.
14
+ *
15
+ * Transition rules, applied per face as the masks are rebuilt:
16
+ *
17
+ * - unpinned → pinned: the face velocity is ZEROED — the static-wall
18
+ * default, and the guard that keeps stale fluid velocity from
19
+ * masquerading as wall motion. A solid author who wants a moving wall
20
+ * writes the face velocity AFTER this recompute (the obstacle system
21
+ * does exactly that).
22
+ * - pinned → pinned: the value is PRESERVED — boundary conditions written
23
+ * by the solid's owner survive subsequent recomputes.
24
+ * - pinned → unpinned: the value is preserved too — fluid uncovered by a
25
+ * retreating wall inherits the wall's velocity, which is the physically
26
+ * sensible seed (and free wake generation).
27
+ *
28
+ * Domain-edge faces (between a cell and the outside) are NOT pinned — the
29
+ * domain boundary is open (ghost pressure 0; see
30
+ * {@link v3_grid_compute_solid_neighbour_mask}) and flow vents through it.
31
+ *
32
+ * Face grids and their indexing:
33
+ *
34
+ * face_x : (res_x+1) × res_y × res_z, index z·(res_x+1)·res_y + y·(res_x+1) + x
35
+ * face (x, y, z) sits between cells (x−1, y, z) and (x, y, z)
36
+ * face_y : res_x × (res_y+1) × res_z, index z·res_x·(res_y+1) + y·res_x + x
37
+ * face_z : res_x × res_y × (res_z+1), index z·res_x·res_y + y·res_x + x
38
+ *
39
+ * O(faces) — two byte-reads per interior face plus the in-place transition
40
+ * comparison. Mask outputs are fully replaced.
41
+ *
42
+ * @param {Uint8Array} face_x Pin mask, length ≥ (res_x+1)·res_y·res_z. Mutated.
43
+ * @param {Uint8Array} face_y Pin mask, length ≥ res_x·(res_y+1)·res_z. Mutated.
44
+ * @param {Uint8Array} face_z Pin mask, length ≥ res_x·res_y·(res_z+1). Mutated.
45
+ * @param {Float32Array} vel_u x-face velocities — zeroed on 0→1 pin transition.
46
+ * @param {Float32Array} vel_v y-face velocities.
47
+ * @param {Float32Array} vel_w z-face velocities.
48
+ * @param {Uint8Array} solid Cell-centered solid flags. Non-zero = solid.
49
+ * @param {number} res_x
50
+ * @param {number} res_y
51
+ * @param {number} res_z
52
+ */
53
+ export function v3_mac_compute_face_solid(face_x, face_y, face_z, vel_u, vel_v, vel_w, solid, res_x, res_y, res_z) {
54
+ const cells = res_x * res_y * res_z;
55
+ assert.greaterThanOrEqual(face_x.length, (res_x + 1) * res_y * res_z, "face_x covers grid");
56
+ assert.greaterThanOrEqual(face_y.length, res_x * (res_y + 1) * res_z, "face_y covers grid");
57
+ assert.greaterThanOrEqual(face_z.length, res_x * res_y * (res_z + 1), "face_z covers grid");
58
+ assert.greaterThanOrEqual(vel_u.length, (res_x + 1) * res_y * res_z, "vel_u covers grid");
59
+ assert.greaterThanOrEqual(vel_v.length, res_x * (res_y + 1) * res_z, "vel_v covers grid");
60
+ assert.greaterThanOrEqual(vel_w.length, res_x * res_y * (res_z + 1), "vel_w covers grid");
61
+ assert.greaterThanOrEqual(solid.length, cells, "solid covers grid");
62
+
63
+ const cell_slice = res_x * res_y;
64
+
65
+ // x-faces.
66
+ const sx = res_x + 1;
67
+ for (let z = 0; z < res_z; z++) {
68
+ const cz = z * cell_slice;
69
+ const fz = z * sx * res_y;
70
+ for (let y = 0; y < res_y; y++) {
71
+ const cy = cz + y * res_x;
72
+ const fy = fz + y * sx;
73
+ // Domain-edge faces are open, never pinned.
74
+ face_x[fy] = 0;
75
+ face_x[fy + res_x] = 0;
76
+ for (let x = 1; x < res_x; x++) {
77
+ const f = fy + x;
78
+ const pinned = (solid[cy + x - 1] !== 0 || solid[cy + x] !== 0) ? 1 : 0;
79
+ if (pinned === 1 && face_x[f] === 0) {
80
+ vel_u[f] = 0;
81
+ }
82
+ face_x[f] = pinned;
83
+ }
84
+ }
85
+ }
86
+
87
+ // y-faces.
88
+ for (let z = 0; z < res_z; z++) {
89
+ const cz = z * cell_slice;
90
+ const fz = z * res_x * (res_y + 1);
91
+ for (let x = 0; x < res_x; x++) {
92
+ face_y[fz + x] = 0;
93
+ face_y[fz + res_y * res_x + x] = 0;
94
+ }
95
+ for (let y = 1; y < res_y; y++) {
96
+ const cy_below = cz + (y - 1) * res_x;
97
+ const cy_above = cz + y * res_x;
98
+ const fy = fz + y * res_x;
99
+ for (let x = 0; x < res_x; x++) {
100
+ const f = fy + x;
101
+ const pinned = (solid[cy_below + x] !== 0 || solid[cy_above + x] !== 0) ? 1 : 0;
102
+ if (pinned === 1 && face_y[f] === 0) {
103
+ vel_v[f] = 0;
104
+ }
105
+ face_y[f] = pinned;
106
+ }
107
+ }
108
+ }
109
+
110
+ // z-faces (face slice size equals the cell slice size).
111
+ for (let y = 0; y < res_y; y++) {
112
+ const row = y * res_x;
113
+ for (let x = 0; x < res_x; x++) {
114
+ face_z[row + x] = 0;
115
+ face_z[res_z * cell_slice + row + x] = 0;
116
+ }
117
+ }
118
+ for (let z = 1; z < res_z; z++) {
119
+ const c_below = (z - 1) * cell_slice;
120
+ const c_above = z * cell_slice;
121
+ const fz = z * cell_slice;
122
+ for (let i = 0; i < cell_slice; i++) {
123
+ const f = fz + i;
124
+ const pinned = (solid[c_below + i] !== 0 || solid[c_above + i] !== 0) ? 1 : 0;
125
+ if (pinned === 1 && face_z[f] === 0) {
126
+ vel_w[f] = 0;
127
+ }
128
+ face_z[f] = pinned;
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Subtract the pressure gradient from a MAC velocity field in place — the
3
+ * face-exact counterpart of the collocated
4
+ * {@link v3_grid_subtract_pressure_gradient}:
5
+ *
6
+ * u(face) −= p(right cell) − p(left cell) (h = 1)
7
+ *
8
+ * Per face:
9
+ * - both adjacent cells fluid: subtract the pressure difference.
10
+ * - PINNED face (either adjacent cell solid): SKIPPED — its stored value is
11
+ * the wall's normal-velocity boundary condition (0 for static solids,
12
+ * the wall speed for movers; see {@link v3_mac_compute_face_solid}) and
13
+ * must survive the projection. Stray deposits are prevented upstream:
14
+ * the mask rebuild zeroes faces on every unpinned→pinned transition and
15
+ * every transport/effector kernel skips pinned faces.
16
+ * - domain-edge face (open boundary): the missing cell is the Dirichlet
17
+ * ghost p = 0, so interior pressure vents flow out through the face.
18
+ *
19
+ * Together with {@link v3_mac_compute_divergence} and the unchanged 7-point
20
+ * solve this forms an exact discrete Helmholtz projection: solve error aside,
21
+ * post-projection divergence is zero — including immediately beside walls,
22
+ * which the collocated form could only approximate.
23
+ *
24
+ * Face grid layout: see {@link v3_mac_compute_face_solid}.
25
+ *
26
+ * @param {Float32Array} vel_u Mutated in place, (res_x+1)·res_y·res_z.
27
+ * @param {Float32Array} vel_v Mutated in place, res_x·(res_y+1)·res_z.
28
+ * @param {Float32Array} vel_w Mutated in place, res_x·res_y·(res_z+1).
29
+ * @param {Float32Array|Float16Array} pressure Cell-centered, from the solve.
30
+ * @param {Uint8Array} face_solid_x From {@link v3_mac_compute_face_solid}.
31
+ * @param {Uint8Array} face_solid_y
32
+ * @param {Uint8Array} face_solid_z
33
+ * @param {number} res_x
34
+ * @param {number} res_y
35
+ * @param {number} res_z
36
+ */
37
+ export function v3_mac_subtract_pressure_gradient(vel_u: Float32Array, vel_v: Float32Array, vel_w: Float32Array, pressure: Float32Array | Float16Array, face_solid_x: Uint8Array, face_solid_y: Uint8Array, face_solid_z: Uint8Array, res_x: number, res_y: number, res_z: number): void;
38
+ //# sourceMappingURL=v3_mac_subtract_pressure_gradient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_subtract_pressure_gradient.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,yDAXW,YAAY,SACZ,YAAY,SACZ,YAAY,YACZ,YAAY,eAAa,gBACzB,UAAU,gBACV,UAAU,gBACV,UAAU,SACV,MAAM,SACN,MAAM,SACN,MAAM,QAmEhB"}