@woosh/meep-engine 2.157.0 → 2.158.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 (60) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/shape/PosedShape3D.d.ts +17 -0
  3. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  4. package/src/core/geom/3d/shape/PosedShape3D.js +50 -0
  5. package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
  6. package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
  7. package/src/engine/graphics/ecs/trail2d/Trail2DFlags.d.ts +1 -0
  8. package/src/engine/graphics/ecs/trail2d/Trail2DFlags.js +9 -1
  9. package/src/engine/physics/fluid/FluidField.d.ts +53 -9
  10. package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
  11. package/src/engine/physics/fluid/FluidField.js +684 -600
  12. package/src/engine/physics/fluid/FluidSimulator.d.ts +53 -38
  13. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  14. package/src/engine/physics/fluid/FluidSimulator.js +252 -178
  15. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +155 -26
  16. package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts +72 -0
  17. package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts.map +1 -0
  18. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -0
  19. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +117 -0
  20. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -0
  21. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -0
  22. package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
  23. package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts +62 -12
  24. package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts.map +1 -1
  25. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +135 -38
  26. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts.map +1 -1
  27. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +85 -38
  28. package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
  29. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +104 -50
  30. package/src/engine/physics/fluid/prototype.js +25 -1
  31. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.d.ts +30 -0
  32. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.d.ts.map +1 -0
  33. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -0
  34. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts +42 -0
  35. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts.map +1 -0
  36. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +319 -0
  37. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.d.ts +53 -0
  38. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.d.ts.map +1 -0
  39. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -0
  40. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.d.ts +46 -0
  41. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.d.ts.map +1 -0
  42. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -0
  43. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts +40 -0
  44. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts.map +1 -0
  45. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +165 -0
  46. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.d.ts +44 -0
  47. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.d.ts.map +1 -0
  48. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -0
  49. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.d.ts +38 -0
  50. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.d.ts.map +1 -0
  51. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -0
  52. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.d.ts +52 -0
  53. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.d.ts.map +1 -0
  54. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -0
  55. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.d.ts +38 -0
  56. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.d.ts.map +1 -0
  57. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -0
  58. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts +0 -41
  59. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts.map +0 -1
  60. package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.js +0 -124
@@ -0,0 +1,92 @@
1
+ import { clamp } from "../../../../core/math/clamp.js";
2
+
3
+ /**
4
+ * Trilinearly sample a cell-centered scalar at a fractional position,
5
+ * EXCLUDING solid cells from the interpolation: solid taps contribute zero
6
+ * weight and the remaining weights are renormalized. This is the
7
+ * free-boundary-aware sampling of Bridson §3.x — without it, a back-trace
8
+ * landing NEAR a wall blends the frozen in-wall value into the fluid, which
9
+ * reads as dye bleeding out of (or being eaten by) walls.
10
+ *
11
+ * When every tap is solid (a trace clipped into a fully walled corner) there
12
+ * is nothing valid to interpolate; `fallback` — typically the destination
13
+ * cell's own previous value, i.e. "no transport this step" — is returned.
14
+ *
15
+ * Positions clamp to the cell lattice exactly like
16
+ * {@link scs3d_sample_linear}; with a zero-filled `solid` grid the result
17
+ * matches that sampler up to floating-point reassociation (the weights sum
18
+ * to 1 and renormalization divides by it).
19
+ *
20
+ * @param {Float32Array} data Cell-centered scalar field.
21
+ * @param {Uint8Array} solid Cell-centered solid flags.
22
+ * @param {number} res_x
23
+ * @param {number} res_y
24
+ * @param {number} res_z
25
+ * @param {number} x
26
+ * @param {number} y
27
+ * @param {number} z
28
+ * @param {number} fallback Returned when no fluid tap is available.
29
+ * @return {number}
30
+ */
31
+ export function v3_grid_sample_scalar_masked(data, solid, res_x, res_y, res_z, x, y, z, fallback) {
32
+ const x_max = res_x - 1;
33
+ const y_max = res_y - 1;
34
+ const z_max = res_z - 1;
35
+
36
+ const x_clamped = clamp(x, 0, x_max);
37
+ const y_clamped = clamp(y, 0, y_max);
38
+ const z_clamped = clamp(z, 0, z_max);
39
+
40
+ const x0 = x_clamped | 0;
41
+ const y0 = y_clamped | 0;
42
+ const z0 = z_clamped | 0;
43
+
44
+ const f_x = x_clamped - x0;
45
+ const f_y = y_clamped - y0;
46
+ const f_z = z_clamped - z0;
47
+
48
+ const x1 = x_clamped === x0 ? x0 : x0 + 1;
49
+ const y1 = y_clamped === y0 ? y0 : y0 + 1;
50
+ const z1 = z_clamped === z0 ? z0 : z0 + 1;
51
+
52
+ const slice = res_x * res_y;
53
+ const z0_off = z0 * slice;
54
+ const z1_off = z1 * slice;
55
+ const y0_off = y0 * res_x;
56
+ const y1_off = y1 * res_x;
57
+
58
+ const c000 = z0_off + y0_off + x0;
59
+ const c100 = z0_off + y0_off + x1;
60
+ const c010 = z0_off + y1_off + x0;
61
+ const c110 = z0_off + y1_off + x1;
62
+ const c001 = z1_off + y0_off + x0;
63
+ const c101 = z1_off + y0_off + x1;
64
+ const c011 = z1_off + y1_off + x0;
65
+ const c111 = z1_off + y1_off + x1;
66
+
67
+ const gx = 1 - f_x;
68
+ const gy = 1 - f_y;
69
+ const gz = 1 - f_z;
70
+
71
+ // Per-tap trilinear weight, zeroed when the tap cell is solid.
72
+ const w000 = solid[c000] !== 0 ? 0 : gx * gy * gz;
73
+ const w100 = solid[c100] !== 0 ? 0 : f_x * gy * gz;
74
+ const w010 = solid[c010] !== 0 ? 0 : gx * f_y * gz;
75
+ const w110 = solid[c110] !== 0 ? 0 : f_x * f_y * gz;
76
+ const w001 = solid[c001] !== 0 ? 0 : gx * gy * f_z;
77
+ const w101 = solid[c101] !== 0 ? 0 : f_x * gy * f_z;
78
+ const w011 = solid[c011] !== 0 ? 0 : gx * f_y * f_z;
79
+ const w111 = solid[c111] !== 0 ? 0 : f_x * f_y * f_z;
80
+
81
+ const total = w000 + w100 + w010 + w110 + w001 + w101 + w011 + w111;
82
+ if (total <= 0) {
83
+ return fallback;
84
+ }
85
+
86
+ const sum = w000 * data[c000] + w100 * data[c100]
87
+ + w010 * data[c010] + w110 * data[c110]
88
+ + w001 * data[c001] + w101 * data[c101]
89
+ + w011 * data[c011] + w111 * data[c111];
90
+
91
+ return sum / total;
92
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Unconditionally stable MacCormack advection of a MAC (face-centered)
3
+ * velocity field — the staggered counterpart of
4
+ * {@link v3_grid_advect_maccormack_velocity}. Per component lattice:
5
+ *
6
+ * 1. forward: û = SL(src, back-trace by carrier·dt)
7
+ * 2. backward: ū = SL(û, trace by +carrier·dt)
8
+ * 3. correct: u* = û + ½·(src − ū)
9
+ * 4. limit: clamp to min/max of the 8 src faces around the back-traced
10
+ * position (Selle et al. 2008 monotone limiter; pinned faces
11
+ * participate — they hold valid boundary values)
12
+ *
13
+ * Solid handling matches {@link v3_mac_advect_sl_velocity}: PINNED faces pass
14
+ * their source value through (the projection owns them as boundary
15
+ * conditions), and every trace — backward in both passes, forward in the
16
+ * corrector — is CLIPPED against solid cells so momentum is never sampled
17
+ * from inside a wall or through one.
18
+ *
19
+ * The cross-component carrier reconstruction uses the exact fixed 4-tap MAC
20
+ * averages (face positions are grid-aligned in the cross axes — see the SL
21
+ * kernel), recomputed per pass; the carrier does not change between passes
22
+ * so both passes see identical trace vectors.
23
+ *
24
+ * Aliasing: per component, `out`, `src` and `fwd` must be distinct, and
25
+ * outputs must not alias any carrier lattice (cross-component carrier
26
+ * sampling — see {@link v3_mac_advect_sl_velocity}).
27
+ *
28
+ * @param {Float32Array[]} outputs `[out_u, out_v, out_w]`. Mutated.
29
+ * @param {Float32Array[]} sources `[src_u, src_v, src_w]`. Read.
30
+ * @param {Float32Array[]} carrier `[car_u, car_v, car_w]`.
31
+ * @param {Float32Array[]} forward_scratch `[fwd_u, fwd_v, fwd_w]`. Mutated.
32
+ * @param {number} res_x
33
+ * @param {number} res_y
34
+ * @param {number} res_z
35
+ * @param {number} time_delta
36
+ * @param {Uint8Array} face_solid_x
37
+ * @param {Uint8Array} face_solid_y
38
+ * @param {Uint8Array} face_solid_z
39
+ * @param {Uint8Array} solid Cell-centered solid flags (trace clipping).
40
+ */
41
+ export function v3_mac_advect_maccormack_velocity(outputs: Float32Array[], sources: Float32Array[], carrier: Float32Array[], forward_scratch: 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;
42
+ //# sourceMappingURL=v3_mac_advect_maccormack_velocity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_advect_maccormack_velocity.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,2DAbW,YAAY,EAAE,WACd,YAAY,EAAE,WACd,YAAY,EAAE,mBACd,YAAY,EAAE,SACd,MAAM,SACN,MAAM,SACN,MAAM,cACN,MAAM,gBACN,UAAU,gBACV,UAAU,gBACV,UAAU,SACV,UAAU,QA0MpB"}
@@ -0,0 +1,319 @@
1
+ import { assert } from "../../../../core/assert.js";
2
+ import { clamp } from "../../../../core/math/clamp.js";
3
+ import { scs3d_sample_linear } from "../../../graphics/texture/3d/scs3d_sample_linear.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
+ * Unconditionally stable MacCormack advection of a MAC (face-centered)
36
+ * velocity field — the staggered counterpart of
37
+ * {@link v3_grid_advect_maccormack_velocity}. Per component lattice:
38
+ *
39
+ * 1. forward: û = SL(src, back-trace by carrier·dt)
40
+ * 2. backward: ū = SL(û, trace by +carrier·dt)
41
+ * 3. correct: u* = û + ½·(src − ū)
42
+ * 4. limit: clamp to min/max of the 8 src faces around the back-traced
43
+ * position (Selle et al. 2008 monotone limiter; pinned faces
44
+ * participate — they hold valid boundary values)
45
+ *
46
+ * Solid handling matches {@link v3_mac_advect_sl_velocity}: PINNED faces pass
47
+ * their source value through (the projection owns them as boundary
48
+ * conditions), and every trace — backward in both passes, forward in the
49
+ * corrector — is CLIPPED against solid cells so momentum is never sampled
50
+ * from inside a wall or through one.
51
+ *
52
+ * The cross-component carrier reconstruction uses the exact fixed 4-tap MAC
53
+ * averages (face positions are grid-aligned in the cross axes — see the SL
54
+ * kernel), recomputed per pass; the carrier does not change between passes
55
+ * so both passes see identical trace vectors.
56
+ *
57
+ * Aliasing: per component, `out`, `src` and `fwd` must be distinct, and
58
+ * outputs must not alias any carrier lattice (cross-component carrier
59
+ * sampling — see {@link v3_mac_advect_sl_velocity}).
60
+ *
61
+ * @param {Float32Array[]} outputs `[out_u, out_v, out_w]`. Mutated.
62
+ * @param {Float32Array[]} sources `[src_u, src_v, src_w]`. Read.
63
+ * @param {Float32Array[]} carrier `[car_u, car_v, car_w]`.
64
+ * @param {Float32Array[]} forward_scratch `[fwd_u, fwd_v, fwd_w]`. Mutated.
65
+ * @param {number} res_x
66
+ * @param {number} res_y
67
+ * @param {number} res_z
68
+ * @param {number} time_delta
69
+ * @param {Uint8Array} face_solid_x
70
+ * @param {Uint8Array} face_solid_y
71
+ * @param {Uint8Array} face_solid_z
72
+ * @param {Uint8Array} solid Cell-centered solid flags (trace clipping).
73
+ */
74
+ export function v3_mac_advect_maccormack_velocity(outputs, sources, carrier, forward_scratch, res_x, res_y, res_z, time_delta, face_solid_x, face_solid_y, face_solid_z, solid) {
75
+ assert.equal(outputs.length, 3, "outputs is a 3-component array");
76
+ assert.equal(sources.length, 3, "sources is a 3-component array");
77
+ assert.equal(carrier.length, 3, "carrier is a 3-component array");
78
+ assert.equal(forward_scratch.length, 3, "forward_scratch is a 3-component array");
79
+
80
+ const out_u = outputs[0], out_v = outputs[1], out_w = outputs[2];
81
+ const src_u = sources[0], src_v = sources[1], src_w = sources[2];
82
+ const car_u = carrier[0], car_v = carrier[1], car_w = carrier[2];
83
+ const fwd_u = forward_scratch[0], fwd_v = forward_scratch[1], fwd_w = forward_scratch[2];
84
+
85
+ assert.notEqual(out_u, src_u, "out_u must not alias src_u");
86
+ assert.notEqual(out_u, fwd_u, "out_u must not alias fwd_u");
87
+ assert.notEqual(src_u, fwd_u, "src_u must not alias fwd_u");
88
+
89
+ const sx = res_x + 1;
90
+ const sy = res_y + 1;
91
+ const sz = res_z + 1;
92
+ const cell_slice = res_x * res_y;
93
+ const u_slice = sx * res_y;
94
+ const v_slice = res_x * sy;
95
+ const last_cx = res_x - 1;
96
+ const last_cy = res_y - 1;
97
+ const last_cz = res_z - 1;
98
+ const dt = time_delta;
99
+
100
+ // ─── u (x-faces): face (x,y,z) sits at (x − 0.5, y, z) ──────────────────
101
+ for (let pass = 0; pass < 2; pass++) {
102
+ for (let z = 0; z < res_z; z++) {
103
+ const f_z = z * u_slice;
104
+ const v_z = z * v_slice;
105
+ const w_z = z * cell_slice;
106
+ for (let y = 0; y < res_y; y++) {
107
+ const f_y = f_z + y * sx;
108
+ const v_lo = v_z + y * res_x;
109
+ const v_hi = v_lo + res_x;
110
+ const w_lo = w_z + y * res_x;
111
+ const w_hi = w_lo + cell_slice;
112
+ for (let x = 0; x <= res_x; x++) {
113
+ const f = f_y + x;
114
+ if (face_solid_x[f] !== 0) {
115
+ if (pass === 0) fwd_u[f] = src_u[f];
116
+ else out_u[f] = src_u[f];
117
+ continue;
118
+ }
119
+ const xa = x > 0 ? x - 1 : 0;
120
+ const xb = x < res_x ? x : last_cx;
121
+ const cu = car_u[f];
122
+ const cv = 0.25 * (car_v[v_lo + xa] + car_v[v_lo + xb] + car_v[v_hi + xa] + car_v[v_hi + xb]);
123
+ const cw = 0.25 * (car_w[w_lo + xa] + car_w[w_lo + xb] + car_w[w_hi + xa] + car_w[w_hi + xb]);
124
+ const px = x - 0.5;
125
+
126
+ // Back-trace end (shared by pass A and the limiter).
127
+ let bx = px - cu * dt;
128
+ let by = y - cv * dt;
129
+ let bz = z - cw * dt;
130
+ if (end_in_solid(solid, res_x, res_y, res_z, bx, by, bz)) {
131
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, px, y, z, bx, by, bz);
132
+ bx = scratch_clip[0]; by = scratch_clip[1]; bz = scratch_clip[2];
133
+ }
134
+
135
+ if (pass === 0) {
136
+ fwd_u[f] = scs3d_sample_linear(src_u, sx, res_y, res_z, bx + 0.5, by, bz);
137
+ continue;
138
+ }
139
+
140
+ // Forward probe through the forward result.
141
+ let qx = px + cu * dt;
142
+ let qy = y + cv * dt;
143
+ let qz = z + cw * dt;
144
+ if (end_in_solid(solid, res_x, res_y, res_z, qx, qy, qz)) {
145
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, px, y, z, qx, qy, qz);
146
+ qx = scratch_clip[0]; qy = scratch_clip[1]; qz = scratch_clip[2];
147
+ }
148
+ const backward = scs3d_sample_linear(fwd_u, sx, res_y, res_z, qx + 0.5, qy, qz);
149
+ const corrected = fwd_u[f] + 0.5 * (src_u[f] - backward);
150
+
151
+ out_u[f] = limit8(corrected, src_u,
152
+ bx + 0.5, by, bz,
153
+ sx, res_y, res_z, sx, u_slice);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ // ─── v (y-faces): face (x,y,z) sits at (x, y − 0.5, z) ──────────────────
160
+ for (let pass = 0; pass < 2; pass++) {
161
+ for (let z = 0; z < res_z; z++) {
162
+ const f_z = z * v_slice;
163
+ const u_z = z * u_slice;
164
+ const w_z = z * cell_slice;
165
+ for (let y = 0; y <= res_y; y++) {
166
+ const f_y = f_z + y * res_x;
167
+ const ya = y > 0 ? y - 1 : 0;
168
+ const yb = y < res_y ? y : last_cy;
169
+ const u_lo = u_z + ya * sx;
170
+ const u_hi = u_z + yb * sx;
171
+ const w_lo = w_z + ya * res_x;
172
+ const w_hi = w_z + yb * res_x;
173
+ for (let x = 0; x < res_x; x++) {
174
+ const f = f_y + x;
175
+ if (face_solid_y[f] !== 0) {
176
+ if (pass === 0) fwd_v[f] = src_v[f];
177
+ else out_v[f] = src_v[f];
178
+ continue;
179
+ }
180
+ 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]);
181
+ const cv = car_v[f];
182
+ 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]);
183
+ const py = y - 0.5;
184
+
185
+ let bx = x - cu * dt;
186
+ let by = py - cv * dt;
187
+ let bz = z - cw * dt;
188
+ if (end_in_solid(solid, res_x, res_y, res_z, bx, by, bz)) {
189
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, py, z, bx, by, bz);
190
+ bx = scratch_clip[0]; by = scratch_clip[1]; bz = scratch_clip[2];
191
+ }
192
+
193
+ if (pass === 0) {
194
+ fwd_v[f] = scs3d_sample_linear(src_v, res_x, sy, res_z, bx, by + 0.5, bz);
195
+ continue;
196
+ }
197
+
198
+ let qx = x + cu * dt;
199
+ let qy = py + cv * dt;
200
+ let qz = z + cw * dt;
201
+ if (end_in_solid(solid, res_x, res_y, res_z, qx, qy, qz)) {
202
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, py, z, qx, qy, qz);
203
+ qx = scratch_clip[0]; qy = scratch_clip[1]; qz = scratch_clip[2];
204
+ }
205
+ const backward = scs3d_sample_linear(fwd_v, res_x, sy, res_z, qx, qy + 0.5, qz);
206
+ const corrected = fwd_v[f] + 0.5 * (src_v[f] - backward);
207
+
208
+ out_v[f] = limit8(corrected, src_v,
209
+ bx, by + 0.5, bz,
210
+ res_x, sy, res_z, res_x, v_slice);
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ // ─── w (z-faces): face (x,y,z) sits at (x, y, z − 0.5) ──────────────────
217
+ for (let pass = 0; pass < 2; pass++) {
218
+ for (let z = 0; z <= res_z; z++) {
219
+ const f_z = z * cell_slice;
220
+ const za = z > 0 ? z - 1 : 0;
221
+ const zb = z < res_z ? z : last_cz;
222
+ const u_lo = za * u_slice;
223
+ const u_hi = zb * u_slice;
224
+ const v_lo = za * v_slice;
225
+ const v_hi = zb * v_slice;
226
+ for (let y = 0; y < res_y; y++) {
227
+ const f_y = f_z + y * res_x;
228
+ const u_row_lo = u_lo + y * sx;
229
+ const u_row_hi = u_hi + y * sx;
230
+ const v_row_lo = v_lo + y * res_x;
231
+ const v_row_hi = v_hi + y * res_x;
232
+ for (let x = 0; x < res_x; x++) {
233
+ const f = f_y + x;
234
+ if (face_solid_z[f] !== 0) {
235
+ if (pass === 0) fwd_w[f] = src_w[f];
236
+ else out_w[f] = src_w[f];
237
+ continue;
238
+ }
239
+ 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]);
240
+ 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]);
241
+ const cw = car_w[f];
242
+ const pz = z - 0.5;
243
+
244
+ let bx = x - cu * dt;
245
+ let by = y - cv * dt;
246
+ let bz = pz - cw * dt;
247
+ if (end_in_solid(solid, res_x, res_y, res_z, bx, by, bz)) {
248
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, y, pz, bx, by, bz);
249
+ bx = scratch_clip[0]; by = scratch_clip[1]; bz = scratch_clip[2];
250
+ }
251
+
252
+ if (pass === 0) {
253
+ fwd_w[f] = scs3d_sample_linear(src_w, res_x, res_y, sz, bx, by, bz + 0.5);
254
+ continue;
255
+ }
256
+
257
+ let qx = x + cu * dt;
258
+ let qy = y + cv * dt;
259
+ let qz = pz + cw * dt;
260
+ if (end_in_solid(solid, res_x, res_y, res_z, qx, qy, qz)) {
261
+ v3_mac_clip_trace(scratch_clip, solid, res_x, res_y, res_z, x, y, pz, qx, qy, qz);
262
+ qx = scratch_clip[0]; qy = scratch_clip[1]; qz = scratch_clip[2];
263
+ }
264
+ const backward = scs3d_sample_linear(fwd_w, res_x, res_y, sz, qx, qy, qz + 0.5);
265
+ const corrected = fwd_w[f] + 0.5 * (src_w[f] - backward);
266
+
267
+ out_w[f] = limit8(corrected, src_w,
268
+ bx, by, bz + 0.5,
269
+ res_x, res_y, sz, res_x, cell_slice);
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Clamp `value` to the min/max of the 8 lattice taps around the fractional
278
+ * position `(lx, ly, lz)` in the lattice's own index space (clamped to its
279
+ * bounds — the same corner set the trilinear sampler interpolates from).
280
+ *
281
+ * @param {number} value
282
+ * @param {Float32Array} data
283
+ * @param {number} lx
284
+ * @param {number} ly
285
+ * @param {number} lz
286
+ * @param {number} dim_x
287
+ * @param {number} dim_y
288
+ * @param {number} dim_z
289
+ * @param {number} row lattice row stride
290
+ * @param {number} slice lattice slice stride
291
+ * @return {number}
292
+ */
293
+ function limit8(value, data, lx, ly, lz, dim_x, dim_y, dim_z, row, slice) {
294
+ const bx = clamp(lx, 0, dim_x - 1);
295
+ const by = clamp(ly, 0, dim_y - 1);
296
+ const bz = clamp(lz, 0, dim_z - 1);
297
+ const x0 = bx | 0;
298
+ const y0 = by | 0;
299
+ const z0 = bz | 0;
300
+ const x1 = bx === x0 ? x0 : x0 + 1;
301
+ const y1 = by === y0 ? y0 : y0 + 1;
302
+ const z1 = bz === z0 ? z0 : z0 + 1;
303
+
304
+ const z0_off = z0 * slice;
305
+ const z1_off = z1 * slice;
306
+ const y0_off = y0 * row;
307
+ const y1_off = y1 * row;
308
+
309
+ let mn = data[z0_off + y0_off + x0];
310
+ let mx = mn;
311
+ let v = data[z0_off + y0_off + x1]; if (v < mn) mn = v; else if (v > mx) mx = v;
312
+ v = data[z0_off + y1_off + x0]; if (v < mn) mn = v; else if (v > mx) mx = v;
313
+ v = data[z0_off + y1_off + x1]; if (v < mn) mn = v; else if (v > mx) mx = v;
314
+ v = data[z1_off + y0_off + x0]; if (v < mn) mn = v; else if (v > mx) mx = v;
315
+ v = data[z1_off + y0_off + x1]; if (v < mn) mn = v; else if (v > mx) mx = v;
316
+ v = data[z1_off + y1_off + x0]; if (v < mn) mn = v; else if (v > mx) mx = v;
317
+ v = data[z1_off + y1_off + x1]; if (v < mn) mn = v; else if (v > mx) mx = v;
318
+ return value < mn ? mn : (value > mx ? mx : value);
319
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Semi-Lagrangian advection of a cell-centered scalar by a MAC velocity
3
+ * field, solid-aware on both ends of the trace:
4
+ *
5
+ * - the back-trace segment is CLIPPED against solid cells
6
+ * ({@link v3_mac_clip_trace}) so a trace can neither sample inside a wall
7
+ * nor tunnel through a thin one into a disconnected pocket;
8
+ * - the sample at the (clipped) source position uses solid-MASKED
9
+ * interpolation ({@link v3_grid_sample_scalar_masked}) so taps inside
10
+ * walls — frozen values from whenever the cell solidified — contribute
11
+ * nothing. Without this, walls slowly leak their frozen contents into
12
+ * passing fluid and absorb dye from it (the stage-5 scalar-mass defect).
13
+ *
14
+ * The carrier velocity at each cell center is the average of the cell's two
15
+ * faces per axis (exact on the staggered layout). Solid destination cells
16
+ * pass their value through unchanged.
17
+ *
18
+ * @param {Float32Array} output Cell-centered destination. Mutated.
19
+ * @param {Float32Array} input Cell-centered source. Must not alias output.
20
+ * @param {Float32Array} vel_u x-face velocities, (res_x+1)·res_y·res_z.
21
+ * @param {Float32Array} vel_v y-face velocities.
22
+ * @param {Float32Array} vel_w z-face velocities.
23
+ * @param {number} res_x
24
+ * @param {number} res_y
25
+ * @param {number} res_z
26
+ * @param {number} time_delta
27
+ * @param {Uint8Array} solid
28
+ */
29
+ export function v3_mac_advect_sl_scalar(output: Float32Array, input: Float32Array, vel_u: Float32Array, vel_v: Float32Array, vel_w: Float32Array, res_x: number, res_y: number, res_z: number, time_delta: number, solid: Uint8Array): void;
30
+ /**
31
+ * Unconditionally stable MacCormack advection of a cell-centered scalar by a
32
+ * MAC velocity field — forward + backward semi-Lagrangian passes, error
33
+ * correction, and Selle's monotone min-max limiter. Solid-aware throughout:
34
+ * both passes clip their traces and sample solid-masked (see
35
+ * {@link v3_mac_advect_sl_scalar}), and the limiter takes its extrema over
36
+ * the FLUID corners around the back-traced position only — when none are
37
+ * fluid the corrected value is discarded in favour of the first-order
38
+ * forward result.
39
+ *
40
+ * @param {Float32Array} output Destination. Mutated.
41
+ * @param {Float32Array} input Source. NOT mutated.
42
+ * @param {Float32Array} forward_scratch Working buffer for the forward pass.
43
+ * @param {Float32Array} vel_u
44
+ * @param {Float32Array} vel_v
45
+ * @param {Float32Array} vel_w
46
+ * @param {number} res_x
47
+ * @param {number} res_y
48
+ * @param {number} res_z
49
+ * @param {number} time_delta
50
+ * @param {Uint8Array} solid
51
+ */
52
+ export function v3_mac_advect_maccormack_scalar(output: Float32Array, input: Float32Array, forward_scratch: Float32Array, vel_u: Float32Array, vel_v: Float32Array, vel_w: Float32Array, res_x: number, res_y: number, res_z: number, time_delta: number, solid: Uint8Array): void;
53
+ //# sourceMappingURL=v3_mac_advect_scalar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v3_mac_advect_scalar.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_mac_advect_scalar.js"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,gDAXW,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,MAAM,SACN,MAAM,SACN,MAAM,cACN,MAAM,SACN,UAAU,QA4CpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wDAZW,YAAY,SACZ,YAAY,mBACZ,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,YAAY,SACZ,MAAM,SACN,MAAM,SACN,MAAM,cACN,MAAM,SACN,UAAU,QA8GpB"}