@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.
- package/package.json +1 -1
- package/src/core/binary/BitSet.d.ts.map +1 -1
- package/src/core/binary/BitSet.js +6 -1
- package/src/core/binary/float_to_uint8.d.ts +4 -1
- package/src/core/binary/float_to_uint8.d.ts.map +1 -1
- package/src/core/binary/float_to_uint8.js +7 -2
- package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.d.ts +11 -1
- package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/query/compute_tight_near_far_clipping_planes.js +37 -14
- package/src/core/geom/3d/shape/PosedShape3D.d.ts +17 -0
- package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PosedShape3D.js +50 -0
- package/src/core/math/gaussian.d.ts +7 -6
- package/src/core/math/gaussian.d.ts.map +1 -1
- package/src/core/math/gaussian.js +9 -8
- package/src/engine/graphics/ecs/trail2d/Trail2D.d.ts.map +1 -1
- package/src/engine/graphics/ecs/trail2d/Trail2D.js +21 -0
- package/src/engine/graphics/ecs/trail2d/Trail2DFlags.d.ts +1 -0
- package/src/engine/graphics/ecs/trail2d/Trail2DFlags.js +9 -1
- package/src/engine/physics/fluid/FluidField.d.ts +53 -9
- package/src/engine/physics/fluid/FluidField.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidField.js +684 -600
- package/src/engine/physics/fluid/FluidSimulator.d.ts +53 -38
- package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidSimulator.js +252 -178
- package/src/engine/physics/fluid/REVIEW_02_PLAN.md +155 -26
- package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts +72 -0
- package/src/engine/physics/fluid/ecs/FluidObstacle.d.ts.map +1 -0
- package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +117 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts.map +1 -0
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -0
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts +62 -12
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +135 -38
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +85 -38
- package/src/engine/physics/fluid/effector/WakeFluidEffector.d.ts.map +1 -1
- package/src/engine/physics/fluid/effector/WakeFluidEffector.js +104 -50
- package/src/engine/physics/fluid/prototype.js +25 -1
- package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.d.ts +30 -0
- package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts +42 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +319 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.d.ts +53 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.d.ts +46 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -0
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts +40 -0
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +165 -0
- package/src/engine/physics/fluid/solver/v3_mac_clip_trace.d.ts +44 -0
- package/src/engine/physics/fluid/solver/v3_mac_clip_trace.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -0
- package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.d.ts +38 -0
- package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -0
- package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.d.ts +52 -0
- package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -0
- package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.d.ts +38 -0
- package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.d.ts.map +1 -0
- package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -0
- package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts +0 -41
- package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.d.ts.map +0 -1
- package/src/engine/physics/fluid/effector/AmbientWindFluidEffector.js +0 -124
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { assert } from "../../../core/assert.js";
|
|
2
2
|
import { sor_optimal_omega } from "../../../core/math/linalg/sor_optimal_omega.js";
|
|
3
|
-
import { v3_grid_advect_maccormack_scalar } from "./solver/v3_grid_advect_maccormack_scalar.js";
|
|
4
|
-
import { v3_grid_advect_maccormack_velocity } from "./solver/v3_grid_advect_maccormack_velocity.js";
|
|
5
|
-
import { v3_grid_advect_sl_velocity } from "./solver/v3_grid_advect_sl_velocity.js";
|
|
6
3
|
import { v3_grid_apply_diffusion } from "./solver/v3_grid_apply_diffusion.js";
|
|
7
|
-
import { v3_grid_apply_vorticity_confinement } from "./solver/v3_grid_apply_vorticity_confinement.js";
|
|
8
|
-
import { v3_grid_apply_scalar_advection } from "./solver/v3_grid_apply_scalar_advection.js";
|
|
9
|
-
import { v3_grid_compute_divergence } from "./solver/v3_grid_compute_divergence.js";
|
|
10
4
|
import { v3_grid_solve_pressure } from "./solver/v3_grid_solve_pressure.js";
|
|
11
5
|
import { v3_grid_solve_pressure_pcg } from "./solver/v3_grid_solve_pressure_pcg.js";
|
|
12
|
-
import {
|
|
6
|
+
import { v3_mac_advect_maccormack_velocity } from "./solver/v3_mac_advect_maccormack_velocity.js";
|
|
7
|
+
import { v3_mac_advect_sl_velocity } from "./solver/v3_mac_advect_sl_velocity.js";
|
|
8
|
+
import { v3_mac_advect_sl_scalar } from "./solver/v3_mac_advect_scalar.js";
|
|
9
|
+
import { v3_mac_apply_vorticity_confinement } from "./solver/v3_mac_apply_vorticity_confinement.js";
|
|
10
|
+
import { v3_mac_compute_divergence } from "./solver/v3_mac_compute_divergence.js";
|
|
11
|
+
import { v3_mac_subtract_pressure_gradient } from "./solver/v3_mac_subtract_pressure_gradient.js";
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* Pressure solver selection. Both solve the same A·p = -div system over the
|
|
@@ -34,7 +33,12 @@ export const PressureSolver = {
|
|
|
34
33
|
};
|
|
35
34
|
|
|
36
35
|
/**
|
|
37
|
-
*
|
|
36
|
+
* VELOCITY transport scheme selection. Passive scalars always use
|
|
37
|
+
* semi-Lagrangian transport regardless of this setting — gather-form
|
|
38
|
+
* MacCormack is non-conservative (the corrector re-adds roughly double the
|
|
39
|
+
* gather bias; measured 1.7x total dye mass in one second of a sealed
|
|
40
|
+
* vortex, independent of wall handling), and scalar conservation is a hard
|
|
41
|
+
* requirement, not a preference.
|
|
38
42
|
*
|
|
39
43
|
* - `"semi-lagrangian"` — first-order back-trace + trilinear gather (Stam
|
|
40
44
|
* 1999). Cheapest; strongly dissipative — a vortex loses half its kinetic
|
|
@@ -42,10 +46,8 @@ export const PressureSolver = {
|
|
|
42
46
|
*
|
|
43
47
|
* - `"maccormack"` — unconditionally stable MacCormack (Selle et al. 2008):
|
|
44
48
|
* a forward and a backward semi-Lagrangian pass with error correction and
|
|
45
|
-
* a monotone min-max limiter. Second-order accurate in space and time
|
|
46
|
-
*
|
|
47
|
-
* next to pressure, so the end-to-end price is small. Allocates 3
|
|
48
|
-
* additional N-sized scratch buffers, shared with the reflection scheme.
|
|
49
|
+
* a monotone min-max limiter. Second-order accurate in space and time.
|
|
50
|
+
* Allocates 3 additional face-lattice scratch buffers.
|
|
49
51
|
*
|
|
50
52
|
* @enum {string}
|
|
51
53
|
*/
|
|
@@ -55,7 +57,10 @@ export const AdvectionScheme = {
|
|
|
55
57
|
};
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
|
-
*
|
|
60
|
+
* 3D incompressible-flow solver on a MAC (staggered) grid — Stam's stable-
|
|
61
|
+
* fluids step structure (1999/2003) with face-centered velocity (Harlow &
|
|
62
|
+
* Welch 1965), an exact discrete Helmholtz projection, and optional
|
|
63
|
+
* advection-reflection / MacCormack transport.
|
|
59
64
|
*
|
|
60
65
|
* Holds tuning knobs plus transient working memory (per-step velocity snapshots,
|
|
61
66
|
* divergence, ping-pong scratch). None of that working memory carries information
|
|
@@ -73,8 +78,8 @@ export const AdvectionScheme = {
|
|
|
73
78
|
* form of the same two projections (see {@link advection_reflection}).
|
|
74
79
|
* Advection itself is semi-Lagrangian or MacCormack per
|
|
75
80
|
* {@link advection_scheme}.
|
|
76
|
-
* 6. For each scalar attribute on the field: optionally diffuse, then
|
|
77
|
-
*
|
|
81
|
+
* 6. For each scalar attribute on the field: optionally diffuse, then
|
|
82
|
+
* advect (always semi-Lagrangian — see {@link AdvectionScheme}).
|
|
78
83
|
*
|
|
79
84
|
* Pressure is warm-started from the previous step's solution (`field.pressure`),
|
|
80
85
|
* which converges much faster than starting from zero once the flow reaches a
|
|
@@ -151,13 +156,19 @@ export class FluidSimulator {
|
|
|
151
156
|
*
|
|
152
157
|
* Why it exists: the solver has no other dissipation mechanism that bounds
|
|
153
158
|
* energy under sustained forcing. Incompressible projection cannot oppose
|
|
154
|
-
* a uniform body force (a uniform field is divergence-free), so a
|
|
155
|
-
* {@link GlobalFluidEffector} accelerates the fluid without
|
|
156
|
-
* is no terminal state. A small damping rate gives constant
|
|
157
|
-
* terminal velocity of approximately
|
|
158
|
-
* and makes transient gusts decay back
|
|
159
|
-
*
|
|
160
|
-
*
|
|
159
|
+
* a uniform body force (a uniform field is divergence-free), so a bare
|
|
160
|
+
* `force` on a {@link GlobalFluidEffector} accelerates the fluid without
|
|
161
|
+
* limit — there is no terminal state. A small damping rate gives constant
|
|
162
|
+
* forcing a terminal velocity of approximately
|
|
163
|
+
* `acceleration / velocity_damping` and makes transient gusts decay back
|
|
164
|
+
* to calm.
|
|
165
|
+
*
|
|
166
|
+
* Relationship to {@link GlobalFluidEffector#drag}: the effector's drag
|
|
167
|
+
* is the same operation expressed as SCENE CONTENT — it relaxes toward a
|
|
168
|
+
* configurable ambient wind and travels with the effector entity. This
|
|
169
|
+
* knob is the SOLVER-level stability primitive: it exists even when no
|
|
170
|
+
* effectors are wired, and always relaxes toward zero. Use the effector
|
|
171
|
+
* for atmosphere design; use this to guarantee boundedness.
|
|
161
172
|
*
|
|
162
173
|
* The exponential form is frame-rate independent: two half-steps damp
|
|
163
174
|
* exactly as much as one full step.
|
|
@@ -202,25 +213,23 @@ export class FluidSimulator {
|
|
|
202
213
|
vorticity_confinement = 0;
|
|
203
214
|
|
|
204
215
|
/**
|
|
205
|
-
* Which transport scheme advects
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* **Default
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* it is the interaction with the collocated pressure floor that is
|
|
219
|
-
* unstable. Flip this on once the grid is staggered.
|
|
216
|
+
* Which transport scheme advects VELOCITY — see {@link AdvectionScheme}
|
|
217
|
+
* (scalars are always semi-Lagrangian; see the enum docstring for why).
|
|
218
|
+
*
|
|
219
|
+
* **Default MacCormack.** On the MAC grid with solid-clipped traces it is
|
|
220
|
+
* measured long-horizon stable (sealed-vortex energy peaks at 0.96x its
|
|
221
|
+
* initial over 10 s, monotone decay thereafter) and retains dramatically
|
|
222
|
+
* more energy than first-order transport: 0.80 vs 0.53 KE after one
|
|
223
|
+
* second (quality.spec.js scenario 2b).
|
|
224
|
+
*
|
|
225
|
+
* Do NOT combine with {@link advection_reflection}: the energy-preserving
|
|
226
|
+
* reflection re-injects the corrector overshoots and the pair pumps
|
|
227
|
+
* energy without bound (10 s peak 3.7x — measured both with and without
|
|
228
|
+
* trace clipping). Reflection is the companion for SEMI_LAGRANGIAN.
|
|
220
229
|
*
|
|
221
230
|
* @type {string}
|
|
222
231
|
*/
|
|
223
|
-
advection_scheme = AdvectionScheme.
|
|
232
|
+
advection_scheme = AdvectionScheme.MACCORMACK;
|
|
224
233
|
|
|
225
234
|
/**
|
|
226
235
|
* Replace the plain "project → advect → project" sequence with the
|
|
@@ -235,25 +244,30 @@ export class FluidSimulator {
|
|
|
235
244
|
*
|
|
236
245
|
* Same two pressure solves as the default double-projection Stam step,
|
|
237
246
|
* and two half-dt advections instead of one full-dt one — measured cost
|
|
238
|
-
*
|
|
247
|
+
* ~+70% of step() at 32×8×32 on the MAC lattices (each half-step pays the
|
|
248
|
+
* cross-component carrier sampling). The reflection replaces the
|
|
239
249
|
* energy-DISSIPATING first projection with an energy-PRESERVING
|
|
240
250
|
* reflection about the divergence-free subspace.
|
|
241
251
|
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* see {@link advection_scheme}
|
|
248
|
-
* staggering.
|
|
252
|
+
* With semi-Lagrangian advection this is measured stable (sealed-vortex
|
|
253
|
+
* kinetic energy never exceeds its initial over 10 s) while retaining
|
|
254
|
+
* visibly more swirl — peak vorticity 0.74 vs 0.68 after one second
|
|
255
|
+
* (quality.spec.js scenario 2b). Do NOT combine with MacCormack — the
|
|
256
|
+
* reflection re-injects the corrector overshoots (10 s energy peak
|
|
257
|
+
* 3.7x); see {@link advection_scheme}.
|
|
249
258
|
*
|
|
250
259
|
* Only consulted when {@link project_before_advection} is true — the
|
|
251
260
|
* scheme is defined around the mid-step projection. With
|
|
252
261
|
* `project_before_advection = false` this flag is ignored.
|
|
253
262
|
*
|
|
263
|
+
* Default OFF: the default velocity scheme is MacCormack, with which the
|
|
264
|
+
* reflection is unstable (see {@link advection_scheme}). Enable it when
|
|
265
|
+
* running SEMI_LAGRANGIAN velocity transport — that pairing is measured
|
|
266
|
+
* stable and retains visibly more swirl than plain SL.
|
|
267
|
+
*
|
|
254
268
|
* @type {boolean}
|
|
255
269
|
*/
|
|
256
|
-
advection_reflection =
|
|
270
|
+
advection_reflection = false;
|
|
257
271
|
|
|
258
272
|
/**
|
|
259
273
|
* Run the pressure projection *before* self-advection in addition to after.
|
|
@@ -318,13 +332,12 @@ export class FluidSimulator {
|
|
|
318
332
|
pressure_iteration_scale = 0.25;
|
|
319
333
|
|
|
320
334
|
// ────────────────────────────────────────────────────────────────────────────
|
|
321
|
-
// Transient scratch — overwritten before read on every step. Cached
|
|
322
|
-
//
|
|
323
|
-
// boundary; if it did it would belong on the field (see
|
|
335
|
+
// Transient scratch — overwritten before read on every step. Cached and
|
|
336
|
+
// grown monotonically so we don't allocate per step. Their value never
|
|
337
|
+
// crosses a step boundary; if it did it would belong on the field (see
|
|
338
|
+
// `field.pressure`). Velocity-shaped buffers are sized per face lattice.
|
|
324
339
|
// ────────────────────────────────────────────────────────────────────────────
|
|
325
340
|
|
|
326
|
-
#scratch_cells = 0;
|
|
327
|
-
|
|
328
341
|
/** @type {Float32Array|null} */ #prev_x = null;
|
|
329
342
|
/** @type {Float32Array|null} */ #prev_y = null;
|
|
330
343
|
/** @type {Float32Array|null} */ #prev_z = null;
|
|
@@ -340,31 +353,46 @@ export class FluidSimulator {
|
|
|
340
353
|
/** @type {Float32Array|null} */ #pcg_As = null;
|
|
341
354
|
/** @type {Float32Array|null} */ #pcg_precon = null;
|
|
342
355
|
|
|
343
|
-
// Auxiliary velocity trio (allocated lazily):
|
|
344
|
-
//
|
|
345
|
-
// overlap in time
|
|
346
|
-
#aux_scratch_cells = 0;
|
|
356
|
+
// Auxiliary velocity trio (allocated lazily, face-lattice sized): the
|
|
357
|
+
// reflection scheme's ũ snapshot / carrier copy, and vorticity
|
|
358
|
+
// confinement's curl scratch — the uses never overlap in time.
|
|
347
359
|
/** @type {Float32Array|null} */ #aux_x = null;
|
|
348
360
|
/** @type {Float32Array|null} */ #aux_y = null;
|
|
349
361
|
/** @type {Float32Array|null} */ #aux_z = null;
|
|
350
362
|
|
|
363
|
+
// MacCormack forward-pass trio (allocated lazily, face-lattice sized).
|
|
364
|
+
// Deliberately separate from #aux: in the reflection path #aux holds the
|
|
365
|
+
// carrier during the very advection that needs this scratch.
|
|
366
|
+
/** @type {Float32Array|null} */ #mc_x = null;
|
|
367
|
+
/** @type {Float32Array|null} */ #mc_y = null;
|
|
368
|
+
/** @type {Float32Array|null} */ #mc_z = null;
|
|
369
|
+
|
|
351
370
|
/**
|
|
352
|
-
* Grow the cached scratch buffers
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
371
|
+
* Grow the cached scratch buffers to fit the field's lattices. The prev
|
|
372
|
+
* trio and the diffusion scratch are sized per FACE grid (the largest of
|
|
373
|
+
* which also covers every cell-count use); divergence and scalar scratch
|
|
374
|
+
* are cell-count. Idempotent and monotonic — never shrinks. Called at the
|
|
375
|
+
* top of {@link step} so user code never has to think about it.
|
|
376
|
+
* @param {FluidField} field
|
|
356
377
|
*/
|
|
357
|
-
#ensure_scratch(
|
|
358
|
-
|
|
359
|
-
|
|
378
|
+
#ensure_scratch(field) {
|
|
379
|
+
const res = field.getResolution();
|
|
380
|
+
const n_u = (res[0] + 1) * res[1] * res[2];
|
|
381
|
+
const n_v = res[0] * (res[1] + 1) * res[2];
|
|
382
|
+
const n_w = res[0] * res[1] * (res[2] + 1);
|
|
383
|
+
const n_cell = field.cellCount();
|
|
384
|
+
const n_face_max = Math.max(n_u, n_v, n_w);
|
|
385
|
+
|
|
386
|
+
if (this.#prev_x === null || this.#prev_x.length < n_u) this.#prev_x = new Float32Array(n_u);
|
|
387
|
+
if (this.#prev_y === null || this.#prev_y.length < n_v) this.#prev_y = new Float32Array(n_v);
|
|
388
|
+
if (this.#prev_z === null || this.#prev_z.length < n_w) this.#prev_z = new Float32Array(n_w);
|
|
389
|
+
if (this.#diffusion_scratch === null || this.#diffusion_scratch.length < n_face_max) {
|
|
390
|
+
this.#diffusion_scratch = new Float32Array(n_face_max);
|
|
360
391
|
}
|
|
361
|
-
this.#
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
this.#divergence = new Float32Array(cell_count);
|
|
366
|
-
this.#diffusion_scratch = new Float32Array(cell_count);
|
|
367
|
-
this.#scalar_scratch = new Float32Array(cell_count);
|
|
392
|
+
if (this.#scalar_scratch === null || this.#scalar_scratch.length < n_cell) {
|
|
393
|
+
this.#scalar_scratch = new Float32Array(n_cell);
|
|
394
|
+
}
|
|
395
|
+
// #divergence is dtype-tracked separately by #ensure_divergence_matches.
|
|
368
396
|
}
|
|
369
397
|
|
|
370
398
|
/**
|
|
@@ -385,53 +413,66 @@ export class FluidSimulator {
|
|
|
385
413
|
}
|
|
386
414
|
|
|
387
415
|
/**
|
|
388
|
-
* Lazily allocate the auxiliary velocity trio (
|
|
389
|
-
*
|
|
416
|
+
* Lazily allocate the auxiliary velocity trio (face-lattice sized) used by
|
|
417
|
+
* the reflection scheme and vorticity confinement. Idempotent and
|
|
390
418
|
* monotonic.
|
|
391
|
-
* @param {
|
|
419
|
+
* @param {FluidField} field
|
|
392
420
|
*/
|
|
393
|
-
#ensure_aux_scratch(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
this.#aux_x = new Float32Array(
|
|
399
|
-
this.#aux_y = new Float32Array(
|
|
400
|
-
this.#aux_z = new Float32Array(
|
|
421
|
+
#ensure_aux_scratch(field) {
|
|
422
|
+
const res = field.getResolution();
|
|
423
|
+
const n_u = (res[0] + 1) * res[1] * res[2];
|
|
424
|
+
const n_v = res[0] * (res[1] + 1) * res[2];
|
|
425
|
+
const n_w = res[0] * res[1] * (res[2] + 1);
|
|
426
|
+
if (this.#aux_x === null || this.#aux_x.length < n_u) this.#aux_x = new Float32Array(n_u);
|
|
427
|
+
if (this.#aux_y === null || this.#aux_y.length < n_v) this.#aux_y = new Float32Array(n_v);
|
|
428
|
+
if (this.#aux_z === null || this.#aux_z.length < n_w) this.#aux_z = new Float32Array(n_w);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Lazily allocate the MacCormack forward-pass trio (face-lattice sized).
|
|
433
|
+
* Idempotent and monotonic.
|
|
434
|
+
* @param {FluidField} field
|
|
435
|
+
*/
|
|
436
|
+
#ensure_mc_scratch(field) {
|
|
437
|
+
const res = field.getResolution();
|
|
438
|
+
const n_u = (res[0] + 1) * res[1] * res[2];
|
|
439
|
+
const n_v = res[0] * (res[1] + 1) * res[2];
|
|
440
|
+
const n_w = res[0] * res[1] * (res[2] + 1);
|
|
441
|
+
if (this.#mc_x === null || this.#mc_x.length < n_u) this.#mc_x = new Float32Array(n_u);
|
|
442
|
+
if (this.#mc_y === null || this.#mc_y.length < n_v) this.#mc_y = new Float32Array(n_v);
|
|
443
|
+
if (this.#mc_z === null || this.#mc_z.length < n_w) this.#mc_z = new Float32Array(n_w);
|
|
401
444
|
}
|
|
402
445
|
|
|
403
446
|
/**
|
|
404
447
|
* Advect the field's velocity from `sources` along the given carrier, into
|
|
405
448
|
* the field's velocity buffers, using the configured
|
|
406
449
|
* {@link advection_scheme}. The carrier may alias the field's own velocity
|
|
407
|
-
* arrays
|
|
450
|
+
* arrays; sources must not.
|
|
408
451
|
*
|
|
409
452
|
* @param {FluidField} field
|
|
410
453
|
* @param {number[]} res
|
|
411
454
|
* @param {number} dt
|
|
412
|
-
* @param {Float32Array[]} sources
|
|
413
|
-
* @param {Float32Array}
|
|
414
|
-
* @param {Float32Array} carrier_y
|
|
415
|
-
* @param {Float32Array} carrier_z
|
|
455
|
+
* @param {Float32Array[]} sources `[src_u, src_v, src_w]` face grids
|
|
456
|
+
* @param {Float32Array[]} carrier `[car_u, car_v, car_w]` face grids
|
|
416
457
|
*/
|
|
417
|
-
#advect_velocity(field, res, dt, sources,
|
|
458
|
+
#advect_velocity(field, res, dt, sources, carrier) {
|
|
418
459
|
const outputs = [field.velocity_x, field.velocity_y, field.velocity_z];
|
|
419
460
|
if (this.advection_scheme === AdvectionScheme.MACCORMACK) {
|
|
420
|
-
this.#
|
|
421
|
-
|
|
422
|
-
outputs, sources,
|
|
423
|
-
|
|
424
|
-
[this.#aux_x, this.#aux_y, this.#aux_z],
|
|
461
|
+
this.#ensure_mc_scratch(field);
|
|
462
|
+
v3_mac_advect_maccormack_velocity(
|
|
463
|
+
outputs, sources, carrier,
|
|
464
|
+
[this.#mc_x, this.#mc_y, this.#mc_z],
|
|
425
465
|
res[0], res[1], res[2],
|
|
426
466
|
dt,
|
|
467
|
+
field.face_solid_x, field.face_solid_y, field.face_solid_z,
|
|
427
468
|
field.solid
|
|
428
469
|
);
|
|
429
470
|
} else {
|
|
430
|
-
|
|
431
|
-
outputs, sources,
|
|
432
|
-
carrier_x, carrier_y, carrier_z,
|
|
471
|
+
v3_mac_advect_sl_velocity(
|
|
472
|
+
outputs, sources, carrier,
|
|
433
473
|
res[0], res[1], res[2],
|
|
434
474
|
dt,
|
|
475
|
+
field.face_solid_x, field.face_solid_y, field.face_solid_z,
|
|
435
476
|
field.solid
|
|
436
477
|
);
|
|
437
478
|
}
|
|
@@ -497,26 +538,31 @@ export class FluidSimulator {
|
|
|
497
538
|
}
|
|
498
539
|
|
|
499
540
|
/**
|
|
500
|
-
* Projection body — assumes
|
|
541
|
+
* Projection body — assumes the field's derived masks (cell neighbour
|
|
542
|
+
* mask, pressure diagonal, face pins) are already fresh.
|
|
543
|
+
*
|
|
544
|
+
* On the MAC layout this is an exact discrete Helmholtz projection: the
|
|
545
|
+
* face-difference divergence and the face gradient compose to exactly the
|
|
546
|
+
* 7-point Laplacian the solvers invert, so post-projection divergence is
|
|
547
|
+
* zero up to solver residual — including beside walls.
|
|
548
|
+
*
|
|
501
549
|
* @param {FluidField} field
|
|
502
550
|
*/
|
|
503
551
|
#project_with_current_mask(field) {
|
|
504
|
-
this.#ensure_scratch(field.cellCount());
|
|
505
552
|
this.#ensure_divergence_matches(field);
|
|
506
553
|
|
|
507
554
|
const res = field.getResolution();
|
|
508
|
-
const solid = field.solid;
|
|
509
555
|
const iterations = this.resolvePressureIterations(res[0], res[1], res[2]);
|
|
510
556
|
|
|
511
557
|
if (iterations === 0) {
|
|
512
558
|
return;
|
|
513
559
|
}
|
|
514
560
|
|
|
515
|
-
|
|
561
|
+
v3_mac_compute_divergence(
|
|
516
562
|
this.#divergence,
|
|
517
563
|
field.velocity_x, field.velocity_y, field.velocity_z,
|
|
518
564
|
res[0], res[1], res[2],
|
|
519
|
-
solid
|
|
565
|
+
field.solid
|
|
520
566
|
);
|
|
521
567
|
|
|
522
568
|
if (this.pressure_solver === PressureSolver.MICPCG) {
|
|
@@ -538,30 +584,42 @@ export class FluidSimulator {
|
|
|
538
584
|
);
|
|
539
585
|
}
|
|
540
586
|
|
|
541
|
-
|
|
587
|
+
v3_mac_subtract_pressure_gradient(
|
|
542
588
|
field.velocity_x, field.velocity_y, field.velocity_z,
|
|
543
589
|
field.pressure,
|
|
544
|
-
|
|
545
|
-
|
|
590
|
+
field.face_solid_x, field.face_solid_y, field.face_solid_z,
|
|
591
|
+
res[0], res[1], res[2]
|
|
546
592
|
);
|
|
547
593
|
}
|
|
548
594
|
|
|
549
595
|
/**
|
|
550
|
-
* Diffuse a single velocity component in-place, using
|
|
551
|
-
* "before" snapshot and `#diffusion_scratch` as
|
|
552
|
-
*
|
|
596
|
+
* Diffuse a single velocity component (one face lattice) in-place, using
|
|
597
|
+
* `source_copy_buf` as the "before" snapshot and `#diffusion_scratch` as
|
|
598
|
+
* the ping-pong scratch. The face-pin mask plays the diffusion kernel's
|
|
599
|
+
* "solid" role: pinned faces are frozen and act as no-flux neighbours.
|
|
600
|
+
*
|
|
601
|
+
* The shared scratch buffers can be larger than this lattice (they are
|
|
602
|
+
* sized to the largest one); the kernel's internal whole-buffer copies
|
|
603
|
+
* require exact-length views.
|
|
604
|
+
*
|
|
605
|
+
* @param {Float32Array} component Face lattice, exact length.
|
|
553
606
|
* @param {Float32Array} source_copy_buf
|
|
554
|
-
* @param {number
|
|
555
|
-
* @param {
|
|
607
|
+
* @param {number} dim_x
|
|
608
|
+
* @param {number} dim_y
|
|
609
|
+
* @param {number} dim_z
|
|
610
|
+
* @param {Uint8Array} face_solid
|
|
556
611
|
*/
|
|
557
|
-
#diffuse_velocity_component(component, source_copy_buf,
|
|
558
|
-
|
|
612
|
+
#diffuse_velocity_component(component, source_copy_buf, dim_x, dim_y, dim_z, face_solid) {
|
|
613
|
+
const n = component.length;
|
|
614
|
+
const copy = source_copy_buf.length === n ? source_copy_buf : source_copy_buf.subarray(0, n);
|
|
615
|
+
const scratch = this.#diffusion_scratch.length === n ? this.#diffusion_scratch : this.#diffusion_scratch.subarray(0, n);
|
|
616
|
+
copy.set(component);
|
|
559
617
|
v3_grid_apply_diffusion(
|
|
560
|
-
component,
|
|
561
|
-
|
|
618
|
+
component, copy, scratch,
|
|
619
|
+
dim_x, dim_y, dim_z,
|
|
562
620
|
this.velocity_diffusion_rate,
|
|
563
621
|
this.velocity_diffusion_iterations,
|
|
564
|
-
|
|
622
|
+
face_solid
|
|
565
623
|
);
|
|
566
624
|
}
|
|
567
625
|
|
|
@@ -595,57 +653,75 @@ export class FluidSimulator {
|
|
|
595
653
|
const cell_count = field.cellCount();
|
|
596
654
|
assert.greaterThan(cell_count, 0, "field has not been built");
|
|
597
655
|
|
|
598
|
-
this.#ensure_scratch(
|
|
656
|
+
this.#ensure_scratch(field);
|
|
599
657
|
|
|
600
658
|
const res = field.getResolution();
|
|
659
|
+
const n_u = field.velocity_x.length;
|
|
660
|
+
const n_v = field.velocity_y.length;
|
|
661
|
+
const n_w = field.velocity_z.length;
|
|
601
662
|
|
|
602
663
|
// 1. External forces.
|
|
603
664
|
for (let i = 0; i < effectors.length; i++) {
|
|
604
665
|
effectors[i].apply(field, time_delta, world_to_grid);
|
|
605
666
|
}
|
|
606
667
|
|
|
668
|
+
// Refresh the pre-baked derived masks (cell neighbour mask, pressure
|
|
669
|
+
// diagonal, face pins) ONCE for the whole step — after effectors (a
|
|
670
|
+
// custom effector may splat solids), before anything that consumes
|
|
671
|
+
// them. Both projections below reuse them. Faces that just became
|
|
672
|
+
// pinned have their velocity zeroed here (static-wall default);
|
|
673
|
+
// already-pinned faces keep whatever boundary condition their solid's
|
|
674
|
+
// owner wrote (FluidObstacleSystem writes moving-wall velocities).
|
|
675
|
+
field.recomputeSolidNeighbourMask();
|
|
676
|
+
|
|
677
|
+
const solid = field.solid;
|
|
678
|
+
|
|
607
679
|
// 1b. Velocity damping — the solver's only energy sink under sustained
|
|
608
|
-
// forcing.
|
|
609
|
-
//
|
|
680
|
+
// forcing. Pinned faces are SKIPPED: their value is the wall's
|
|
681
|
+
// boundary condition, not fluid momentum.
|
|
610
682
|
if (this.velocity_damping > 0) {
|
|
611
683
|
const decay = Math.exp(-this.velocity_damping * time_delta);
|
|
612
|
-
const
|
|
613
|
-
const
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
684
|
+
const vu = field.velocity_x;
|
|
685
|
+
const vv = field.velocity_y;
|
|
686
|
+
const vw = field.velocity_z;
|
|
687
|
+
const pin_u = field.face_solid_x;
|
|
688
|
+
const pin_v = field.face_solid_y;
|
|
689
|
+
const pin_w = field.face_solid_z;
|
|
690
|
+
for (let i = 0; i < n_u; i++) {
|
|
691
|
+
if (pin_u[i] === 0) vu[i] *= decay;
|
|
692
|
+
}
|
|
693
|
+
for (let i = 0; i < n_v; i++) {
|
|
694
|
+
if (pin_v[i] === 0) vv[i] *= decay;
|
|
695
|
+
}
|
|
696
|
+
for (let i = 0; i < n_w; i++) {
|
|
697
|
+
if (pin_w[i] === 0) vw[i] *= decay;
|
|
619
698
|
}
|
|
620
699
|
}
|
|
621
700
|
|
|
622
701
|
// 1c. Vorticity confinement — an additional force term that sharpens
|
|
623
|
-
// vortex cores (see the knob's docstring).
|
|
624
|
-
// the
|
|
625
|
-
// later phases anyway.
|
|
702
|
+
// vortex cores (see the knob's docstring). Needs the fresh face
|
|
703
|
+
// pins; uses the prev + aux trios and the diffusion scratch as
|
|
704
|
+
// working memory, all overwritten by later phases anyway.
|
|
626
705
|
if (this.vorticity_confinement > 0) {
|
|
627
|
-
|
|
706
|
+
this.#ensure_aux_scratch(field);
|
|
707
|
+
v3_mac_apply_vorticity_confinement(
|
|
628
708
|
field.velocity_x, field.velocity_y, field.velocity_z,
|
|
629
|
-
|
|
709
|
+
field.face_solid_x, field.face_solid_y, field.face_solid_z,
|
|
710
|
+
this.#prev_x, this.#prev_y, this.#prev_z,
|
|
711
|
+
this.#aux_x, this.#aux_y, this.#aux_z, this.#diffusion_scratch,
|
|
630
712
|
res[0], res[1], res[2],
|
|
631
713
|
this.vorticity_confinement * time_delta,
|
|
632
|
-
|
|
714
|
+
solid
|
|
633
715
|
);
|
|
634
716
|
}
|
|
635
717
|
|
|
636
|
-
//
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
// byte pass versus recomputing per projection.
|
|
640
|
-
field.recomputeSolidNeighbourMask();
|
|
641
|
-
|
|
642
|
-
const solid = field.solid;
|
|
643
|
-
|
|
644
|
-
// 2. Velocity diffusion (viscosity). Cheap to skip when disabled.
|
|
718
|
+
// 2. Velocity diffusion (viscosity). Cheap to skip when disabled. Each
|
|
719
|
+
// face lattice diffuses with its own dimensions; the face-pin masks
|
|
720
|
+
// make walls no-flux exactly like solid cells used to.
|
|
645
721
|
if (this.velocity_diffusion_rate > 0 && this.velocity_diffusion_iterations > 0) {
|
|
646
|
-
this.#diffuse_velocity_component(field.velocity_x, this.#prev_x, res,
|
|
647
|
-
this.#diffuse_velocity_component(field.velocity_y, this.#prev_y, res,
|
|
648
|
-
this.#diffuse_velocity_component(field.velocity_z, this.#prev_z, res,
|
|
722
|
+
this.#diffuse_velocity_component(field.velocity_x, this.#prev_x, res[0] + 1, res[1], res[2], field.face_solid_x);
|
|
723
|
+
this.#diffuse_velocity_component(field.velocity_y, this.#prev_y, res[0], res[1] + 1, res[2], field.face_solid_y);
|
|
724
|
+
this.#diffuse_velocity_component(field.velocity_z, this.#prev_z, res[0], res[1], res[2] + 1, field.face_solid_z);
|
|
649
725
|
}
|
|
650
726
|
|
|
651
727
|
// 3-5. Transport block: project + self-advect (+ optional reflection).
|
|
@@ -656,17 +732,15 @@ export class FluidSimulator {
|
|
|
656
732
|
// the `advection_reflection` docstring. Same two projections as the
|
|
657
733
|
// plain path; two half-dt advections instead of one full-dt one.
|
|
658
734
|
const half_dt = 0.5 * time_delta;
|
|
659
|
-
const n = cell_count;
|
|
660
735
|
|
|
661
736
|
// ũ = advect(u, u, dt/2)
|
|
662
737
|
this.#prev_x.set(field.velocity_x);
|
|
663
738
|
this.#prev_y.set(field.velocity_y);
|
|
664
739
|
this.#prev_z.set(field.velocity_z);
|
|
665
|
-
this.#advect_velocity(field, res, half_dt, prev,
|
|
740
|
+
this.#advect_velocity(field, res, half_dt, prev, prev);
|
|
666
741
|
|
|
667
|
-
// Snapshot
|
|
668
|
-
|
|
669
|
-
this.#ensure_aux_scratch(n);
|
|
742
|
+
// Snapshot ũ.
|
|
743
|
+
this.#ensure_aux_scratch(field);
|
|
670
744
|
this.#aux_x.set(field.velocity_x);
|
|
671
745
|
this.#aux_y.set(field.velocity_y);
|
|
672
746
|
this.#aux_z.set(field.velocity_z);
|
|
@@ -674,19 +748,23 @@ export class FluidSimulator {
|
|
|
674
748
|
// u½ = project(ũ)
|
|
675
749
|
this.#project_with_current_mask(field);
|
|
676
750
|
|
|
677
|
-
// û = 2·u½ − ũ — the reflection about the divergence-free subspace
|
|
751
|
+
// û = 2·u½ − ũ — the reflection about the divergence-free subspace,
|
|
752
|
+
// per face lattice.
|
|
678
753
|
const ax = this.#aux_x, ay = this.#aux_y, az = this.#aux_z;
|
|
679
754
|
const fx = field.velocity_x, fy = field.velocity_y, fz = field.velocity_z;
|
|
680
755
|
const px = this.#prev_x, py = this.#prev_y, pz = this.#prev_z;
|
|
681
|
-
for (let i = 0; i <
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
this.#
|
|
756
|
+
for (let i = 0; i < n_u; i++) px[i] = 2 * fx[i] - ax[i];
|
|
757
|
+
for (let i = 0; i < n_v; i++) py[i] = 2 * fy[i] - ay[i];
|
|
758
|
+
for (let i = 0; i < n_w; i++) pz[i] = 2 * fz[i] - az[i];
|
|
759
|
+
|
|
760
|
+
// u′ = advect(û, u½, dt/2). The advection kernels sample the
|
|
761
|
+
// carrier's OTHER lattices spatially, so the carrier must not
|
|
762
|
+
// alias the outputs — copy u½ into the aux trio (ũ there is dead,
|
|
763
|
+
// already folded into û).
|
|
764
|
+
this.#aux_x.set(fx);
|
|
765
|
+
this.#aux_y.set(fy);
|
|
766
|
+
this.#aux_z.set(fz);
|
|
767
|
+
this.#advect_velocity(field, res, half_dt, prev, [ax, ay, az]);
|
|
690
768
|
|
|
691
769
|
// u₁ = project(u′)
|
|
692
770
|
this.#project_with_current_mask(field);
|
|
@@ -699,11 +777,11 @@ export class FluidSimulator {
|
|
|
699
777
|
this.#prev_x.set(field.velocity_x);
|
|
700
778
|
this.#prev_y.set(field.velocity_y);
|
|
701
779
|
this.#prev_z.set(field.velocity_z);
|
|
702
|
-
this.#advect_velocity(field, res, time_delta, prev,
|
|
780
|
+
this.#advect_velocity(field, res, time_delta, prev, prev);
|
|
703
781
|
|
|
704
782
|
// Re-project after advection. Solids cannot have changed since the
|
|
705
783
|
// top-of-step mask refresh (only the simulator has touched the
|
|
706
|
-
// field), so the
|
|
784
|
+
// field), so the masks are still fresh.
|
|
707
785
|
this.#project_with_current_mask(field);
|
|
708
786
|
}
|
|
709
787
|
|
|
@@ -714,8 +792,13 @@ export class FluidSimulator {
|
|
|
714
792
|
|
|
715
793
|
if (this.scalar_diffusion_rate > 0 && this.scalar_diffusion_iterations > 0) {
|
|
716
794
|
this.#scalar_scratch.set(attr.data);
|
|
795
|
+
// The diffusion scratch is face-lattice sized; the kernel's
|
|
796
|
+
// internal whole-buffer copies need an exact cell-count view.
|
|
797
|
+
const ping_pong = this.#diffusion_scratch.length === cell_count
|
|
798
|
+
? this.#diffusion_scratch
|
|
799
|
+
: this.#diffusion_scratch.subarray(0, cell_count);
|
|
717
800
|
v3_grid_apply_diffusion(
|
|
718
|
-
attr.data, this.#scalar_scratch,
|
|
801
|
+
attr.data, this.#scalar_scratch, ping_pong,
|
|
719
802
|
res[0], res[1], res[2],
|
|
720
803
|
this.scalar_diffusion_rate,
|
|
721
804
|
this.scalar_diffusion_iterations,
|
|
@@ -724,25 +807,16 @@ export class FluidSimulator {
|
|
|
724
807
|
}
|
|
725
808
|
|
|
726
809
|
this.#scalar_scratch.set(attr.data);
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
} else {
|
|
738
|
-
v3_grid_apply_scalar_advection(
|
|
739
|
-
attr.data, this.#scalar_scratch,
|
|
740
|
-
field.velocity_x, field.velocity_y, field.velocity_z,
|
|
741
|
-
res[0], res[1], res[2],
|
|
742
|
-
time_delta,
|
|
743
|
-
solid
|
|
744
|
-
);
|
|
745
|
-
}
|
|
810
|
+
// Scalars are ALWAYS semi-Lagrangian, independent of
|
|
811
|
+
// advection_scheme — gather-MacCormack is non-conservative (see
|
|
812
|
+
// the AdvectionScheme docstring).
|
|
813
|
+
v3_mac_advect_sl_scalar(
|
|
814
|
+
attr.data, this.#scalar_scratch,
|
|
815
|
+
field.velocity_x, field.velocity_y, field.velocity_z,
|
|
816
|
+
res[0], res[1], res[2],
|
|
817
|
+
time_delta,
|
|
818
|
+
solid
|
|
819
|
+
);
|
|
746
820
|
}
|
|
747
821
|
}
|
|
748
822
|
}
|