@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,236 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { clamp } from "../../../../core/math/clamp.js";
3
+ import { v3_grid_sample_scalar_masked } from "./v3_grid_sample_scalar_masked.js";
4
+ import { v3_mac_clip_trace } from "./v3_mac_clip_trace.js";
5
+
6
+ const scratch_clip = new Float32Array(3);
7
+
8
+ /**
9
+ * Straight-line (loop-free, inlinable) test: is the cell containing the
10
+ * point solid? Same rounding/clamping as v3_mac_clip_trace. Kernels use this
11
+ * inline per trace and call the loop-bearing clip helper only on a hit — see
12
+ * the performance contract on {@link v3_mac_clip_trace}.
13
+ *
14
+ * @param {Uint8Array} solid
15
+ * @param {number} res_x
16
+ * @param {number} res_y
17
+ * @param {number} res_z
18
+ * @param {number} px
19
+ * @param {number} py
20
+ * @param {number} pz
21
+ * @return {boolean}
22
+ */
23
+ function end_in_solid(solid, res_x, res_y, res_z, px, py, pz) {
24
+ let x = (px + 0.5) | 0;
25
+ let y = (py + 0.5) | 0;
26
+ let z = (pz + 0.5) | 0;
27
+ if (x < 0) x = 0; else if (x >= res_x) x = res_x - 1;
28
+ if (y < 0) y = 0; else if (y >= res_y) y = res_y - 1;
29
+ if (z < 0) z = 0; else if (z >= res_z) z = res_z - 1;
30
+ return solid[z * res_x * res_y + y * res_x + x] !== 0;
31
+ }
32
+
33
+
34
+ /**
35
+ * Semi-Lagrangian advection of a cell-centered scalar by a MAC velocity
36
+ * field, solid-aware on both ends of the trace:
37
+ *
38
+ * - the back-trace segment is CLIPPED against solid cells
39
+ * ({@link v3_mac_clip_trace}) so a trace can neither sample inside a wall
40
+ * nor tunnel through a thin one into a disconnected pocket;
41
+ * - the sample at the (clipped) source position uses solid-MASKED
42
+ * interpolation ({@link v3_grid_sample_scalar_masked}) so taps inside
43
+ * walls — frozen values from whenever the cell solidified — contribute
44
+ * nothing. Without this, walls slowly leak their frozen contents into
45
+ * passing fluid and absorb dye from it (the stage-5 scalar-mass defect).
46
+ *
47
+ * The carrier velocity at each cell center is the average of the cell's two
48
+ * faces per axis (exact on the staggered layout). Solid destination cells
49
+ * pass their value through unchanged.
50
+ *
51
+ * @param {Float32Array} output Cell-centered destination. Mutated.
52
+ * @param {Float32Array} input Cell-centered source. Must not alias output.
53
+ * @param {Float32Array} vel_u x-face velocities, (res_x+1)·res_y·res_z.
54
+ * @param {Float32Array} vel_v y-face velocities.
55
+ * @param {Float32Array} vel_w z-face velocities.
56
+ * @param {number} res_x
57
+ * @param {number} res_y
58
+ * @param {number} res_z
59
+ * @param {number} time_delta
60
+ * @param {Uint8Array} solid
61
+ */
62
+ export function v3_mac_advect_sl_scalar(output, input, vel_u, vel_v, vel_w, res_x, res_y, res_z, time_delta, solid) {
63
+ assert.notEqual(output, input, "output must not alias input");
64
+ const cell_count = res_x * res_y * res_z;
65
+ assert.greaterThanOrEqual(output.length, cell_count, "output covers grid");
66
+ assert.greaterThanOrEqual(input.length, cell_count, "input covers grid");
67
+
68
+ const cell_slice = res_x * res_y;
69
+ const sx = res_x + 1;
70
+ const v_slice = res_x * (res_y + 1);
71
+
72
+ for (let z = 0; z < res_z; z++) {
73
+ const c_z = z * cell_slice;
74
+ const u_z = z * sx * res_y;
75
+ const v_z = z * v_slice;
76
+ for (let y = 0; y < res_y; y++) {
77
+ const c_y = c_z + y * res_x;
78
+ const u_y = u_z + y * sx;
79
+ const v_lo = v_z + y * res_x;
80
+ const w_lo = c_z + y * res_x;
81
+ for (let x = 0; x < res_x; x++) {
82
+ const c = c_y + x;
83
+ if (solid[c] !== 0) {
84
+ output[c] = input[c];
85
+ continue;
86
+ }
87
+ const cu = 0.5 * (vel_u[u_y + x] + vel_u[u_y + x + 1]);
88
+ const cv = 0.5 * (vel_v[v_lo + x] + vel_v[v_lo + res_x + x]);
89
+ const cw = 0.5 * (vel_w[w_lo + x] + vel_w[w_lo + cell_slice + x]);
90
+
91
+ let bx = x - cu * time_delta;
92
+ let by = y - cv * time_delta;
93
+ let bz = z - cw * time_delta;
94
+ if (end_in_solid(solid, res_x, res_y, res_z, bx, by, bz)) {
95
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, y, z, bx, by, bz);
96
+ bx = scratch_clip[0]; by = scratch_clip[1]; bz = scratch_clip[2];
97
+ }
98
+
99
+ output[c] = v3_grid_sample_scalar_masked(input, solid, res_x, res_y, res_z,
100
+ bx, by, bz, input[c]);
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Unconditionally stable MacCormack advection of a cell-centered scalar by a
108
+ * MAC velocity field — forward + backward semi-Lagrangian passes, error
109
+ * correction, and Selle's monotone min-max limiter. Solid-aware throughout:
110
+ * both passes clip their traces and sample solid-masked (see
111
+ * {@link v3_mac_advect_sl_scalar}), and the limiter takes its extrema over
112
+ * the FLUID corners around the back-traced position only — when none are
113
+ * fluid the corrected value is discarded in favour of the first-order
114
+ * forward result.
115
+ *
116
+ * @param {Float32Array} output Destination. Mutated.
117
+ * @param {Float32Array} input Source. NOT mutated.
118
+ * @param {Float32Array} forward_scratch Working buffer for the forward pass.
119
+ * @param {Float32Array} vel_u
120
+ * @param {Float32Array} vel_v
121
+ * @param {Float32Array} vel_w
122
+ * @param {number} res_x
123
+ * @param {number} res_y
124
+ * @param {number} res_z
125
+ * @param {number} time_delta
126
+ * @param {Uint8Array} solid
127
+ */
128
+ export function v3_mac_advect_maccormack_scalar(output, input, forward_scratch, vel_u, vel_v, vel_w, res_x, res_y, res_z, time_delta, solid) {
129
+ assert.notEqual(output, input, "output must not alias input");
130
+ assert.notEqual(output, forward_scratch, "output must not alias forward_scratch");
131
+ assert.notEqual(input, forward_scratch, "input must not alias forward_scratch");
132
+
133
+ const cell_count = res_x * res_y * res_z;
134
+ assert.greaterThanOrEqual(forward_scratch.length, cell_count, "forward_scratch covers grid");
135
+
136
+ const cell_slice = res_x * res_y;
137
+ const sx = res_x + 1;
138
+ const v_slice = res_x * (res_y + 1);
139
+ const last_x = res_x - 1;
140
+ const last_y = res_y - 1;
141
+ const last_z = res_z - 1;
142
+
143
+ const fwd = forward_scratch;
144
+
145
+ // ─── pass 1: forward SL step into the scratch ─────────────────────────────
146
+ v3_mac_advect_sl_scalar(fwd, input, vel_u, vel_v, vel_w, res_x, res_y, res_z, time_delta, solid);
147
+
148
+ // ─── pass 2: backward step, correction, monotone limit ──────────────────
149
+ for (let z = 0; z < res_z; z++) {
150
+ const c_z = z * cell_slice;
151
+ const u_z = z * sx * res_y;
152
+ const v_z = z * v_slice;
153
+ for (let y = 0; y < res_y; y++) {
154
+ const c_y = c_z + y * res_x;
155
+ const u_y = u_z + y * sx;
156
+ const v_lo = v_z + y * res_x;
157
+ const w_lo = c_z + y * res_x;
158
+ for (let x = 0; x < res_x; x++) {
159
+ const c = c_y + x;
160
+ if (solid[c] !== 0) {
161
+ output[c] = input[c];
162
+ continue;
163
+ }
164
+
165
+ const cu = 0.5 * (vel_u[u_y + x] + vel_u[u_y + x + 1]) * time_delta;
166
+ const cv = 0.5 * (vel_v[v_lo + x] + vel_v[v_lo + res_x + x]) * time_delta;
167
+ const cw = 0.5 * (vel_w[w_lo + x] + vel_w[w_lo + cell_slice + x]) * time_delta;
168
+
169
+ // Reverse probe through the forward result, clipped + masked.
170
+ let qx = x + cu;
171
+ let qy = y + cv;
172
+ let qz = z + cw;
173
+ if (end_in_solid(solid, res_x, res_y, res_z, qx, qy, qz)) {
174
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, y, z, qx, qy, qz);
175
+ qx = scratch_clip[0]; qy = scratch_clip[1]; qz = scratch_clip[2];
176
+ }
177
+ const backward = v3_grid_sample_scalar_masked(fwd, solid, res_x, res_y, res_z,
178
+ qx, qy, qz, fwd[c]);
179
+
180
+ const corrected = fwd[c] + 0.5 * (input[c] - backward);
181
+
182
+ // Limiter bounds: extrema of the FLUID cells among the 8 around
183
+ // the (clipped) back-traced position.
184
+ let tx = x - cu;
185
+ let ty = y - cv;
186
+ let tz = z - cw;
187
+ if (end_in_solid(solid, res_x, res_y, res_z, tx, ty, tz)) {
188
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, y, z, tx, ty, tz);
189
+ tx = scratch_clip[0]; ty = scratch_clip[1]; tz = scratch_clip[2];
190
+ }
191
+ const bx = clamp(tx, 0, last_x);
192
+ const by = clamp(ty, 0, last_y);
193
+ const bz = clamp(tz, 0, last_z);
194
+ const x0 = bx | 0;
195
+ const y0 = by | 0;
196
+ const z0 = bz | 0;
197
+ const x1 = bx === x0 ? x0 : x0 + 1;
198
+ const y1 = by === y0 ? y0 : y0 + 1;
199
+ const z1 = bz === z0 ? z0 : z0 + 1;
200
+
201
+ const z0_off = z0 * cell_slice;
202
+ const z1_off = z1 * cell_slice;
203
+ const y0_off = y0 * res_x;
204
+ const y1_off = y1 * res_x;
205
+
206
+ let mn = Infinity;
207
+ let mx = -Infinity;
208
+ let t = z0_off + y0_off + x0;
209
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
210
+ t = z0_off + y0_off + x1;
211
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
212
+ t = z0_off + y1_off + x0;
213
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
214
+ t = z0_off + y1_off + x1;
215
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
216
+ t = z1_off + y0_off + x0;
217
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
218
+ t = z1_off + y0_off + x1;
219
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
220
+ t = z1_off + y1_off + x0;
221
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
222
+ t = z1_off + y1_off + x1;
223
+ if (solid[t] === 0) { const v = input[t]; if (v < mn) mn = v; if (v > mx) mx = v; }
224
+
225
+ if (mn === Infinity) {
226
+ // No fluid corner to bound against — fall back to the
227
+ // first-order forward value.
228
+ output[c] = fwd[c];
229
+ continue;
230
+ }
231
+
232
+ output[c] = corrected < mn ? mn : (corrected > mx ? mx : corrected);
233
+ }
234
+ }
235
+ }
236
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Semi-Lagrangian advection of a MAC (face-centered) velocity field along a
3
+ * separate MAC carrier field.
4
+ *
5
+ * Each component is updated on its own face lattice: for every face, the full
6
+ * carrier velocity AT the face position is reconstructed (own component read
7
+ * directly, the two cross components trilinearly interpolated from their
8
+ * lattices — the classic MAC 4-face average falls out of the interpolation),
9
+ * the position is traced backward by `carrier · dt`, and the SOURCE component
10
+ * is sampled at the traced position on its own lattice.
11
+ *
12
+ * PINNED faces (either adjacent cell solid — see
13
+ * {@link v3_mac_compute_face_solid}) pass their source value through
14
+ * unchanged: their velocity is a boundary condition owned by the projection,
15
+ * not transported state.
16
+ *
17
+ * Back-traces are CLIPPED against solid cells ({@link v3_mac_clip_trace}):
18
+ * a trace stops at the wall instead of sampling momentum from inside it or
19
+ * from a disconnected pocket on the far side. Sampling the face lattices
20
+ * near walls needs no masking — pinned faces hold valid boundary values.
21
+ *
22
+ * Position convention (cell-center coordinates): x-face `(i, j, k)` sits at
23
+ * `(i − 0.5, j, k)`; sampling component grids therefore offsets by +0.5 on
24
+ * the component's own axis. All positions clamp at the lattice edges.
25
+ *
26
+ * Aliasing: `outputs[i]` must not alias `sources[i]`, and outputs must not
27
+ * alias ANY carrier lattice — the per-face carrier reconstruction samples the
28
+ * OTHER components' lattices spatially, so an output lattice written earlier
29
+ * in the kernel would be re-read as carrier data later. Pass a copy when the
30
+ * carrier is the field being advected into (the simulator's reflection path
31
+ * does exactly that).
32
+ *
33
+ * @param {Float32Array[]} outputs `[out_u, out_v, out_w]` face grids. Mutated.
34
+ * @param {Float32Array[]} sources `[src_u, src_v, src_w]`. Read.
35
+ * @param {Float32Array[]} carrier `[car_u, car_v, car_w]`. Drives the traces.
36
+ * @param {number} res_x
37
+ * @param {number} res_y
38
+ * @param {number} res_z
39
+ * @param {number} time_delta
40
+ * @param {Uint8Array} face_solid_x Pinned-face masks, one per lattice.
41
+ * @param {Uint8Array} face_solid_y
42
+ * @param {Uint8Array} face_solid_z
43
+ * @param {Uint8Array} solid Cell-centered solid flags (trace clipping).
44
+ */
45
+ export function v3_mac_advect_sl_velocity(outputs: Float32Array[], sources: Float32Array[], carrier: Float32Array[], res_x: number, res_y: number, res_z: number, time_delta: number, face_solid_x: Uint8Array, face_solid_y: Uint8Array, face_solid_z: Uint8Array, solid: Uint8Array): void;
46
+ //# sourceMappingURL=v3_mac_advect_sl_velocity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_advect_sl_velocity.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,mDAZW,YAAY,EAAE,WACd,YAAY,EAAE,WACd,YAAY,EAAE,SACd,MAAM,SACN,MAAM,SACN,MAAM,cACN,MAAM,gBACN,UAAU,gBACV,UAAU,gBACV,UAAU,SACV,UAAU,QAwKpB"}
@@ -0,0 +1,217 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { scs3d_sample_linear } from "../../../graphics/texture/3d/scs3d_sample_linear.js";
3
+ import { v3_mac_clip_trace } from "./v3_mac_clip_trace.js";
4
+
5
+ const scratch_clip = new Float32Array(3);
6
+
7
+ /**
8
+ * Semi-Lagrangian advection of a MAC (face-centered) velocity field along a
9
+ * separate MAC carrier field.
10
+ *
11
+ * Each component is updated on its own face lattice: for every face, the full
12
+ * carrier velocity AT the face position is reconstructed (own component read
13
+ * directly, the two cross components trilinearly interpolated from their
14
+ * lattices — the classic MAC 4-face average falls out of the interpolation),
15
+ * the position is traced backward by `carrier · dt`, and the SOURCE component
16
+ * is sampled at the traced position on its own lattice.
17
+ *
18
+ * PINNED faces (either adjacent cell solid — see
19
+ * {@link v3_mac_compute_face_solid}) pass their source value through
20
+ * unchanged: their velocity is a boundary condition owned by the projection,
21
+ * not transported state.
22
+ *
23
+ * Back-traces are CLIPPED against solid cells ({@link v3_mac_clip_trace}):
24
+ * a trace stops at the wall instead of sampling momentum from inside it or
25
+ * from a disconnected pocket on the far side. Sampling the face lattices
26
+ * near walls needs no masking — pinned faces hold valid boundary values.
27
+ *
28
+ * Position convention (cell-center coordinates): x-face `(i, j, k)` sits at
29
+ * `(i − 0.5, j, k)`; sampling component grids therefore offsets by +0.5 on
30
+ * the component's own axis. All positions clamp at the lattice edges.
31
+ *
32
+ * Aliasing: `outputs[i]` must not alias `sources[i]`, and outputs must not
33
+ * alias ANY carrier lattice — the per-face carrier reconstruction samples the
34
+ * OTHER components' lattices spatially, so an output lattice written earlier
35
+ * in the kernel would be re-read as carrier data later. Pass a copy when the
36
+ * carrier is the field being advected into (the simulator's reflection path
37
+ * does exactly that).
38
+ *
39
+ * @param {Float32Array[]} outputs `[out_u, out_v, out_w]` face grids. Mutated.
40
+ * @param {Float32Array[]} sources `[src_u, src_v, src_w]`. Read.
41
+ * @param {Float32Array[]} carrier `[car_u, car_v, car_w]`. Drives the traces.
42
+ * @param {number} res_x
43
+ * @param {number} res_y
44
+ * @param {number} res_z
45
+ * @param {number} time_delta
46
+ * @param {Uint8Array} face_solid_x Pinned-face masks, one per lattice.
47
+ * @param {Uint8Array} face_solid_y
48
+ * @param {Uint8Array} face_solid_z
49
+ * @param {Uint8Array} solid Cell-centered solid flags (trace clipping).
50
+ */
51
+ export function v3_mac_advect_sl_velocity(outputs, sources, carrier, res_x, res_y, res_z, time_delta, face_solid_x, face_solid_y, face_solid_z, solid) {
52
+ assert.equal(outputs.length, 3, "outputs is a 3-component array");
53
+ assert.equal(sources.length, 3, "sources is a 3-component array");
54
+ assert.equal(carrier.length, 3, "carrier is a 3-component array");
55
+
56
+ const out_u = outputs[0], out_v = outputs[1], out_w = outputs[2];
57
+ const src_u = sources[0], src_v = sources[1], src_w = sources[2];
58
+ const car_u = carrier[0], car_v = carrier[1], car_w = carrier[2];
59
+
60
+ assert.notEqual(out_u, src_u, "out_u must not alias src_u");
61
+ assert.notEqual(out_v, src_v, "out_v must not alias src_v");
62
+ assert.notEqual(out_w, src_w, "out_w must not alias src_w");
63
+
64
+ const sx = res_x + 1;
65
+ const sy = res_y + 1;
66
+ const sz = res_z + 1;
67
+ const cell_slice = res_x * res_y;
68
+ const u_slice = sx * res_y;
69
+ const v_slice = res_x * sy;
70
+ const last_cx = res_x - 1;
71
+ const last_cy = res_y - 1;
72
+ const last_cz = res_z - 1;
73
+
74
+ // Cross-component carrier reconstruction: a face position is grid-aligned
75
+ // in both cross axes, so the "trilinear sample" of the other lattice
76
+ // degenerates to the classic MAC 4-tap average with fixed ¼ weights —
77
+ // computed inline below instead of paying the general sampler. Only the
78
+ // tap index along the face's own axis needs edge clamping (the face
79
+ // lattice extends half a cell past the cross lattice there); the trilinear
80
+ // clamp the sampler would apply reduces to the same duplicated tap.
81
+
82
+ // ─── u (x-faces) ─────────────────────────────────────────────────────────
83
+ for (let z = 0; z < res_z; z++) {
84
+ const f_z = z * u_slice;
85
+ const v_z = z * v_slice;
86
+ const w_z = z * cell_slice;
87
+ for (let y = 0; y < res_y; y++) {
88
+ const f_y = f_z + y * sx;
89
+ const v_lo = v_z + y * res_x;
90
+ const v_hi = v_lo + res_x;
91
+ const w_lo = w_z + y * res_x;
92
+ const w_hi = w_lo + cell_slice;
93
+ for (let x = 0; x <= res_x; x++) {
94
+ const f = f_y + x;
95
+ if (face_solid_x[f] !== 0) {
96
+ out_u[f] = src_u[f];
97
+ continue;
98
+ }
99
+ // Cross taps straddle the face in x: cells x−1 and x, clamped
100
+ // to the cross lattice's x range [0, res_x−1].
101
+ const xa = x > 0 ? x - 1 : 0;
102
+ const xb = x < res_x ? x : last_cx;
103
+ const cu = car_u[f];
104
+ const cv = 0.25 * (car_v[v_lo + xa] + car_v[v_lo + xb] + car_v[v_hi + xa] + car_v[v_hi + xb]);
105
+ const cw = 0.25 * (car_w[w_lo + xa] + car_w[w_lo + xb] + car_w[w_hi + xa] + car_w[w_hi + xb]);
106
+ // Face position is (x − 0.5, y, z) in cell coords. Inline the
107
+ // end-cell fluid test (see v3_mac_clip_trace performance
108
+ // contract); the loop-bearing clip helper runs only on a hit.
109
+ const ex = x - 0.5 - cu * time_delta;
110
+ const ey = y - cv * time_delta;
111
+ const ez = z - cw * time_delta;
112
+ let ecx = (ex + 0.5) | 0;
113
+ let ecy = (ey + 0.5) | 0;
114
+ let ecz = (ez + 0.5) | 0;
115
+ if (ecx < 0) ecx = 0; else if (ecx >= res_x) ecx = last_cx;
116
+ if (ecy < 0) ecy = 0; else if (ecy >= res_y) ecy = last_cy;
117
+ if (ecz < 0) ecz = 0; else if (ecz >= res_z) ecz = last_cz;
118
+ if (solid[ecz * cell_slice + ecy * res_x + ecx] === 0) {
119
+ out_u[f] = scs3d_sample_linear(src_u, sx, res_y, res_z, ex + 0.5, ey, ez);
120
+ } else {
121
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z,
122
+ x - 0.5, y, z, ex, ey, ez);
123
+ out_u[f] = scs3d_sample_linear(src_u, sx, res_y, res_z,
124
+ scratch_clip[0] + 0.5, scratch_clip[1], scratch_clip[2]);
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ // ─── v (y-faces) ─────────────────────────────────────────────────────────
131
+ for (let z = 0; z < res_z; z++) {
132
+ const f_z = z * v_slice;
133
+ const u_z = z * u_slice;
134
+ const w_z = z * cell_slice;
135
+ for (let y = 0; y <= res_y; y++) {
136
+ const f_y = f_z + y * res_x;
137
+ const ya = y > 0 ? y - 1 : 0;
138
+ const yb = y < res_y ? y : last_cy;
139
+ const u_lo = u_z + ya * sx;
140
+ const u_hi = u_z + yb * sx;
141
+ const w_lo = w_z + ya * res_x;
142
+ const w_hi = w_z + yb * res_x;
143
+ for (let x = 0; x < res_x; x++) {
144
+ const f = f_y + x;
145
+ if (face_solid_y[f] !== 0) {
146
+ out_v[f] = src_v[f];
147
+ continue;
148
+ }
149
+ const cu = 0.25 * (car_u[u_lo + x] + car_u[u_lo + x + 1] + car_u[u_hi + x] + car_u[u_hi + x + 1]);
150
+ const cv = car_v[f];
151
+ const cw = 0.25 * (car_w[w_lo + x] + car_w[w_lo + x + cell_slice] + car_w[w_hi + x] + car_w[w_hi + x + cell_slice]);
152
+ const ex = x - cu * time_delta;
153
+ const ey = y - 0.5 - cv * time_delta;
154
+ const ez = z - cw * time_delta;
155
+ let ecx = (ex + 0.5) | 0;
156
+ let ecy = (ey + 0.5) | 0;
157
+ let ecz = (ez + 0.5) | 0;
158
+ if (ecx < 0) ecx = 0; else if (ecx >= res_x) ecx = last_cx;
159
+ if (ecy < 0) ecy = 0; else if (ecy >= res_y) ecy = last_cy;
160
+ if (ecz < 0) ecz = 0; else if (ecz >= res_z) ecz = last_cz;
161
+ if (solid[ecz * cell_slice + ecy * res_x + ecx] === 0) {
162
+ out_v[f] = scs3d_sample_linear(src_v, res_x, sy, res_z, ex, ey + 0.5, ez);
163
+ } else {
164
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z,
165
+ x, y - 0.5, z, ex, ey, ez);
166
+ out_v[f] = scs3d_sample_linear(src_v, res_x, sy, res_z,
167
+ scratch_clip[0], scratch_clip[1] + 0.5, scratch_clip[2]);
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ // ─── w (z-faces) ─────────────────────────────────────────────────────────
174
+ for (let z = 0; z <= res_z; z++) {
175
+ const f_z = z * cell_slice;
176
+ const za = z > 0 ? z - 1 : 0;
177
+ const zb = z < res_z ? z : last_cz;
178
+ const u_lo = za * u_slice;
179
+ const u_hi = zb * u_slice;
180
+ const v_lo = za * v_slice;
181
+ const v_hi = zb * v_slice;
182
+ for (let y = 0; y < res_y; y++) {
183
+ const f_y = f_z + y * res_x;
184
+ const u_row_lo = u_lo + y * sx;
185
+ const u_row_hi = u_hi + y * sx;
186
+ const v_row_lo = v_lo + y * res_x;
187
+ const v_row_hi = v_hi + y * res_x;
188
+ for (let x = 0; x < res_x; x++) {
189
+ const f = f_y + x;
190
+ if (face_solid_z[f] !== 0) {
191
+ out_w[f] = src_w[f];
192
+ continue;
193
+ }
194
+ const cu = 0.25 * (car_u[u_row_lo + x] + car_u[u_row_lo + x + 1] + car_u[u_row_hi + x] + car_u[u_row_hi + x + 1]);
195
+ const cv = 0.25 * (car_v[v_row_lo + x] + car_v[v_row_lo + x + res_x] + car_v[v_row_hi + x] + car_v[v_row_hi + x + res_x]);
196
+ const cw = car_w[f];
197
+ const ex = x - cu * time_delta;
198
+ const ey = y - cv * time_delta;
199
+ const ez = z - 0.5 - cw * time_delta;
200
+ let ecx = (ex + 0.5) | 0;
201
+ let ecy = (ey + 0.5) | 0;
202
+ let ecz = (ez + 0.5) | 0;
203
+ if (ecx < 0) ecx = 0; else if (ecx >= res_x) ecx = last_cx;
204
+ if (ecy < 0) ecy = 0; else if (ecy >= res_y) ecy = last_cy;
205
+ if (ecz < 0) ecz = 0; else if (ecz >= res_z) ecz = last_cz;
206
+ if (solid[ecz * cell_slice + ecy * res_x + ecx] === 0) {
207
+ out_w[f] = scs3d_sample_linear(src_w, res_x, res_y, sz, ex, ey, ez + 0.5);
208
+ } else {
209
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z,
210
+ x, y, z - 0.5, ex, ey, ez);
211
+ out_w[f] = scs3d_sample_linear(src_w, res_x, res_y, sz,
212
+ scratch_clip[0], scratch_clip[1], scratch_clip[2] + 0.5);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Vorticity confinement (Fedkiw, Stam, Jensen 2001) for a MAC velocity field.
3
+ *
4
+ * Three passes:
5
+ *
6
+ * 1. average the face velocities to cell centers (exact on the staggered
7
+ * layout: two faces per axis),
8
+ * 2. ω = ∇×v and |ω| at centers by central differences (interior cells;
9
+ * the boundary ring and solid cells get 0),
10
+ * 3. the confinement force f = ε·(N × ω), N = ∇|ω|/|∇|ω||, is computed per
11
+ * cell and scattered to the cell's six faces at half weight each —
12
+ * interior faces accumulate halves from both neighbours, recovering the
13
+ * face-centered average of the cell forces. Pinned faces (adjacent
14
+ * solid) receive nothing.
15
+ *
16
+ * See {@link v3_grid_apply_vorticity_confinement} for the scheme rationale
17
+ * and the energy note (confinement ADDS energy; pair with damping).
18
+ *
19
+ * @param {Float32Array} vel_u Mutated in place, (res_x+1)·res_y·res_z.
20
+ * @param {Float32Array} vel_v Mutated in place.
21
+ * @param {Float32Array} vel_w Mutated in place.
22
+ * @param {Uint8Array} face_solid_x Pinned-face masks.
23
+ * @param {Uint8Array} face_solid_y
24
+ * @param {Uint8Array} face_solid_z
25
+ * @param {Float32Array} scratch_cu Cell-count working buffers (callers may
26
+ * pass larger, e.g. face-sized, arrays). All mutated.
27
+ * @param {Float32Array} scratch_cv
28
+ * @param {Float32Array} scratch_cw
29
+ * @param {Float32Array} scratch_wx
30
+ * @param {Float32Array} scratch_wy
31
+ * @param {Float32Array} scratch_wz
32
+ * @param {Float32Array} scratch_wmag
33
+ * @param {number} res_x
34
+ * @param {number} res_y
35
+ * @param {number} res_z
36
+ * @param {number} epsilon_dt Confinement strength ε times the timestep.
37
+ * @param {Uint8Array} solid
38
+ */
39
+ export function v3_mac_apply_vorticity_confinement(vel_u: Float32Array, vel_v: Float32Array, vel_w: Float32Array, face_solid_x: Uint8Array, face_solid_y: Uint8Array, face_solid_z: Uint8Array, scratch_cu: Float32Array, scratch_cv: Float32Array, scratch_cw: Float32Array, scratch_wx: Float32Array, scratch_wy: Float32Array, scratch_wz: Float32Array, scratch_wmag: Float32Array, res_x: number, res_y: number, res_z: number, epsilon_dt: number, solid: Uint8Array): void;
40
+ //# sourceMappingURL=v3_mac_apply_vorticity_confinement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_apply_vorticity_confinement.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,0DApBW,YAAY,SACZ,YAAY,SACZ,YAAY,gBACZ,UAAU,gBACV,UAAU,gBACV,UAAU,cACV,YAAY,cAEZ,YAAY,cACZ,YAAY,cACZ,YAAY,cACZ,YAAY,cACZ,YAAY,gBACZ,YAAY,SACZ,MAAM,SACN,MAAM,SACN,MAAM,cACN,MAAM,SACN,UAAU,QA8HpB"}