@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
|
@@ -7,23 +7,23 @@ public API of effectors + `FluidField` frozen (state shape may change).
|
|
|
7
7
|
|
|
8
8
|
## Quality gate baselines (quality.spec.js)
|
|
9
9
|
|
|
10
|
-
| metric | stage 0 (baseline) | stage
|
|
10
|
+
| metric | stage 0 (baseline) | stage 2 | stage 3+4 | **stage 5 (MAC)** | notes |
|
|
11
11
|
|---|---|---|---|---|---|
|
|
12
|
-
| projection linear residual, SOR | 1.21 |
|
|
13
|
-
| projection linear residual, PCG | 3.34e-2 |
|
|
14
|
-
| cold post-div
|
|
15
|
-
| warm post-div, SOR | 9.22 |
|
|
16
|
-
| warm post-div, PCG | 1.84 |
|
|
17
|
-
| vortex KE retention (60 steps) | 0.498 | 0.
|
|
18
|
-
| vortex peak-ω retention | 0.606 | 0.
|
|
19
|
-
| wall penetration (seed 8) | 5.72 |
|
|
20
|
-
| scalar mass retention | 0.231 | 0.
|
|
21
|
-
| diffusion wall leak | 16.9 |
|
|
22
|
-
| sealed-box gravity max\|vy\| | 1.24 | 1.24 |
|
|
23
|
-
| checkerboard residual (10 steps) | 0.593 | 0.
|
|
24
|
-
| damped gravity terminal \|v\| | — |
|
|
25
|
-
|
|
|
26
|
-
| step() 32×8×32 (ms) | 1.16
|
|
12
|
+
| projection linear residual, SOR | 1.21 | 0.67 | 0.67 | 0.92 | 8 sweeps @ 16³; RHS grew 14.5→17.8 with face differencing |
|
|
13
|
+
| projection linear residual, PCG | 3.34e-2 | 1.16e-2 | 1.16e-2 | 1.39e-2 | iteration/tolerance-capped |
|
|
14
|
+
| cold post-div | 11.2 | 9.1 | 9.1 | **0.91 / 1.4e-2** | SOR / PCG. Now EQUALS the linear residual: D∘G ≡ L, the operator floor is gone |
|
|
15
|
+
| warm post-div, SOR | 9.22 | 0.70 | 0.35 | **9.6e-3** | 1000× vs stage 0 |
|
|
16
|
+
| warm post-div, PCG | 1.84 | 0.70 | 0.35 | **3.1e-5** | |
|
|
17
|
+
| vortex KE retention (60 steps) | 0.498 | 0.495 | 0.527 | 0.530 | sealed box, default SL+reflection |
|
|
18
|
+
| vortex peak-ω retention | 0.606 | 0.624 | 0.725 | **0.736** | |
|
|
19
|
+
| wall penetration (seed 8) | 5.72 | 6.96 | 6.65 | **0 (exact)** | MAC pins wall-face normal velocity outright |
|
|
20
|
+
| scalar mass retention | 0.231 | 0.849 | 0.837 | 0.818 | residual loss = gather SL + traces through the shell |
|
|
21
|
+
| diffusion wall leak | 16.9 | 0 | 0 | 0 | stage-1 fix holds |
|
|
22
|
+
| sealed-box gravity max\|vy\| | 1.24 | 1.24 | 0.97 | **0.070** | near-exact hydrostatic balance |
|
|
23
|
+
| checkerboard residual (10 steps) | 0.593 | 0.627 | 0.587 | **0.058** | pure compressive mode; collocated was blind to it |
|
|
24
|
+
| damped gravity terminal \|v\| | — | 4.92 = analytic | 4.92 | 4.92 | bounded forcing (was unbounded growth) |
|
|
25
|
+
| atmosphere terminal velocity | — | 8.00/8 | 8.00/8 | (8.00, −5.00) = analytic | GlobalFluidEffector wind+force+drag holds `wind + force/drag` exactly |
|
|
26
|
+
| step() 32×8×32 / 32³ / 64³ (ms) | 1.16 / 4.38 / 54.0 | 1.44 / 5.11 / — | 1.25 / 3.91 / 42.7 | 2.56 / 7.39 / 68.5 | MAC ≈ 2× collocated: 3 lattices + cross-component carriers; levers listed under follow-ups |
|
|
27
27
|
|
|
28
28
|
Perf reference (review probe, this machine): step() 32×8×32 = 1.16 ms,
|
|
29
29
|
32³ = 4.4 ms, 64³ = 54 ms. Division→reciprocal-table in SOR measured 14%
|
|
@@ -58,9 +58,13 @@ SLOWER in V8 — do not do it.
|
|
|
58
58
|
- `velocity_damping` knob on the simulator (exponential, default 0) — the
|
|
59
59
|
solver's only energy sink under sustained forcing; gives constant forces
|
|
60
60
|
a terminal velocity.
|
|
61
|
-
- New
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
- New ambient-wind drive — relaxes toward a target wind velocity instead of
|
|
62
|
+
integrating constant acceleration (no unbounded accumulation; frame-rate
|
|
63
|
+
independent). Shipped first as a separate `AmbientWindFluidEffector`,
|
|
64
|
+
later UNIFIED into `GlobalFluidEffector` as the `wind` + `drag` fields of
|
|
65
|
+
the atmosphere model `dv/dt = force + drag·(wind − v)` (exact-ODE
|
|
66
|
+
integrator, terminal velocity `wind + force/drag`) — one global effector
|
|
67
|
+
instead of two competing ones.
|
|
64
68
|
- [x] **Stage 3 — transport quality.** (213 tests green.)
|
|
65
69
|
- Fused 3-channel trilinear sampler (`scs3d_sample_linear3`); generalized
|
|
66
70
|
SL velocity kernel with separate carrier; MacCormack velocity + scalar
|
|
@@ -86,13 +90,138 @@ SLOWER in V8 — do not do it.
|
|
|
86
90
|
`advection_reflection = true`. Reflection alone is a clean win: stable,
|
|
87
91
|
+20% step cost, warm steady-state divergence halves again (0.71 → 0.35),
|
|
88
92
|
sealed-gravity settles better (1.24 → 0.97).
|
|
89
|
-
- [
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
- [x] **Stage 4 — perf polish (bench-gated).** (219 tests green; quality gate
|
|
94
|
+
bit-identical.)
|
|
95
|
+
- SOR interior fast path (`mask === 63` → branch-free constant-diagonal
|
|
96
|
+
body, no diag read): measured **37% faster** per solve at 64³, noise-level
|
|
97
|
+
at 32×8×32, bit-identical results. Adopted.
|
|
98
|
+
- Rejected by measurement (stage-0 probe): division → reciprocal-table in
|
|
99
|
+
the SOR update was 14% SLOWER in V8. Recorded so nobody retries it.
|
|
100
|
+
- `vorticity_confinement` knob (Fedkiw 2001), default off — two O(N) passes
|
|
101
|
+
on existing scratch when enabled; pairs with `velocity_damping`.
|
|
102
|
+
- Final step() medians (this machine): 32×8×32 = 1.25 ms, 32³ = 3.91 ms,
|
|
103
|
+
64³ = 42.7 ms — at 32³ and up faster than the stage-0 baseline despite
|
|
104
|
+
reflection being on.
|
|
105
|
+
- [x] **Stage 5 — MAC staggering migration (user-approved).** 223 tests green.
|
|
106
|
+
- Layout: `velocity_x/y/z` are face lattices ((rx+1)·ry·rz etc.); new derived
|
|
107
|
+
`face_solid_x/y/z` pin masks (either adjacent cell solid → face velocity is
|
|
108
|
+
a boundary condition). `setVelocityAt`/`addVelocityAt` write both faces per
|
|
109
|
+
axis; `sampleVelocity` interpolates per staggered lattice. Public
|
|
110
|
+
signatures unchanged.
|
|
111
|
+
- Kernels: new `v3_mac_*` divergence / gradient / SL + MacCormack advection
|
|
112
|
+
(velocity + scalar) / face-solid / vorticity confinement. The pressure
|
|
113
|
+
solvers and the mask/diag builder needed ZERO changes — stage 2's
|
|
114
|
+
open-boundary diagonal already was the MAC system. The legacy `v3_grid_*`
|
|
115
|
+
collocated kernels remain as standalone utilities with their specs.
|
|
116
|
+
- Exactness confirmed by the gate: cold post-divergence == linear residual
|
|
117
|
+
(D∘G ≡ L); wall-face penetration exactly 0; hydrostatic balance to 0.07;
|
|
118
|
+
the compressive checkerboard is annihilated (0.59 → 0.058).
|
|
119
|
+
- Scheme matrix on MAC: MC velocity is now long-horizon STABLE (10 s peak
|
|
120
|
+
0.96 vs collocated 1.9×), but (a) MC+reflection still pumps (3.6×) and
|
|
121
|
+
(b) MC SCALAR transport still inflates mass ~1.7× — back-traces sample
|
|
122
|
+
through solid walls and the corrector amplifies the bleed. Defaults stay
|
|
123
|
+
SL + reflection; MacCormack documented opt-in.
|
|
124
|
+
- Perf: 2.56 / 7.39 / 68.5 ms at 32×8×32 / 32³ / 64³ (~2× collocated). The
|
|
125
|
+
SL kernel's cross-component carrier reconstruction was optimized from
|
|
126
|
+
general trilinear to the exact fixed 4-tap MAC average (−31..39% step),
|
|
127
|
+
pinned by a trilinear-reference equivalence spec.
|
|
128
|
+
|
|
129
|
+
## Phase A (post-MAC) — solid-aware traces + MacCormack default. DONE.
|
|
130
|
+
|
|
131
|
+
- Back-traces (and MacCormack probes) are CLIPPED against solid cells
|
|
132
|
+
(`v3_mac_clip_trace`, bisection); scalar samples are solid-MASKED with
|
|
133
|
+
weight renormalization (`v3_grid_sample_scalar_masked`). Kills momentum
|
|
134
|
+
tunnelling through thin walls (spec-pinned) and wall dye bleed.
|
|
135
|
+
- **Negative finding, gate-corrected:** MacCormack SCALAR mass inflation
|
|
136
|
+
(1.71×) was NOT wall sampling — bit-identical before/after the fix. It is
|
|
137
|
+
the corrector re-adding ~double the gather bias: intrinsic
|
|
138
|
+
non-conservation. Scalars are therefore ALWAYS semi-Lagrangian
|
|
139
|
+
(hard-coded; `advection_scheme` selects the VELOCITY scheme only).
|
|
140
|
+
- MC + reflection re-probed WITH clipping: still pumps (10 s peak 3.7×) —
|
|
141
|
+
intrinsic to the pairing, stays barred.
|
|
142
|
+
- **Defaults: `advection_scheme = MACCORMACK` (velocity),
|
|
143
|
+
`advection_reflection = false`.** Gate: vortex KE retention 0.53 → 0.80
|
|
144
|
+
on the default path; sealed gravity 0.070 → 0.047; warm divergence
|
|
145
|
+
settles at 2.96e-2 / 2.8e-4 (SOR/PCG — one mid-step projection fewer than
|
|
146
|
+
SL+refl's 9.6e-3 / 3.1e-5; still ~300× below pre-MAC).
|
|
147
|
+
- **Perf lesson (measured 8.6×!):** V8 refuses to inline loop-bearing
|
|
148
|
+
callees — calling `v3_mac_clip_trace` per face made the SL kernel 8.6×
|
|
149
|
+
slower. Hot kernels now inline the straight-line "end cell solid?" test
|
|
150
|
+
and call the bisection helper only on wall hits (performance contract
|
|
151
|
+
documented on the helper). Final step(): MC default 2.74 ms @ 32×8×32 /
|
|
152
|
+
10.4 ms @ 32³; SL+refl 1.97 / 7.35.
|
|
153
|
+
|
|
154
|
+
## Phase B — FluidObstacleSystem. DONE.
|
|
155
|
+
|
|
156
|
+
- `FluidObstacle` component (inflation knob) + `FluidObstacleSystem`:
|
|
157
|
+
voxelizes each (FluidObstacle, Collider, Transform) entity's shape into
|
|
158
|
+
every overlapping FluidComponent's solid mask per tick, via
|
|
159
|
+
`PosedShape3D.signed_distance_at_point` (new posed point queries on
|
|
160
|
+
PosedShape3D, spec'd). Rigid pose — Transform.scale ignored, matching the
|
|
161
|
+
physics collider convention. Cell solid iff sdf(centre) <= inflation;
|
|
162
|
+
iteration clipped to the shape AABB.
|
|
163
|
+
- Ownership contract: with >= 1 obstacle the system clears + rebuilds EVERY
|
|
164
|
+
field's mask each tick (moving obstacles leave no stale walls); with none
|
|
165
|
+
it touches nothing. Register BEFORE FluidSystem.
|
|
166
|
+
- 8 system tests incl. rotation, inflation-for-thin-walls, origin offset,
|
|
167
|
+
broad-phase, and a same-tick FluidSystem integration check.
|
|
168
|
+
|
|
169
|
+
## Phase C — moving-solid face velocity BCs. DONE.
|
|
170
|
+
|
|
171
|
+
- **Pinned faces are the wall's normal-velocity BC**, not dead storage:
|
|
172
|
+
- `v3_mac_compute_face_solid` takes the velocity arrays and zeroes a face
|
|
173
|
+
only on the unpinned→pinned TRANSITION (static-wall default).
|
|
174
|
+
Pinned→pinned recomputes preserve the stored BC; pinned→unpinned keeps
|
|
175
|
+
the wall velocity behind as the uncovered fluid's wake seed. Spec'd
|
|
176
|
+
(`v3_mac_compute_face_solid.spec.js`).
|
|
177
|
+
- `v3_mac_compute_divergence` reads pinned faces at face value (the
|
|
178
|
+
face_solid params are gone) — this is the entire transmission mechanism:
|
|
179
|
+
a stamped wall face shows up as inflow/outflow in the adjacent fluid
|
|
180
|
+
cell and the projection does the rest.
|
|
181
|
+
- The pressure gradient, advection, damping and effectors all SKIP pinned
|
|
182
|
+
faces, so a stamped BC survives the whole step bit-exactly (asserted in
|
|
183
|
+
quality 8).
|
|
184
|
+
- `FluidObstacleSystem` second pass: per obstacle, the wall velocity is
|
|
185
|
+
**`RigidBody.linearVelocity`** (world m/s — the obstacle tuple is the full
|
|
186
|
+
physics tuple FluidObstacle+Collider+RigidBody+Transform); cells it
|
|
187
|
+
voxelized get all six face slots stamped with the grid-units velocity.
|
|
188
|
+
The system refreshes each field's masks BEFORE stamping so the transition
|
|
189
|
+
zeroing is consumed there and the stamped values stick through the
|
|
190
|
+
simulator's own per-step refresh.
|
|
191
|
+
- **Velocity source rationale** (replaced the initial Transform-delta
|
|
192
|
+
finite difference in the same session): pose differencing turns teleports
|
|
193
|
+
(spawn snaps, network corrections) into one-tick air detonations — the
|
|
194
|
+
exact failure mode BodyKind.KinematicPosition was deferred over in the
|
|
195
|
+
contact solver. Reading the body instead: air and contacts agree on wall
|
|
196
|
+
speed, first tick stamps (no `_prev` warm-up), no system-private pose
|
|
197
|
+
state to lose on snapshot restore, sleeping bodies stamp nothing (solver
|
|
198
|
+
zeroes velocities on sleep), and `angularVelocity` sits ready for the
|
|
199
|
+
ω×r follow-up. Pose-animated colliders read as stationary walls that
|
|
200
|
+
teleport — for air just as for contacts; movers go through
|
|
201
|
+
KinematicVelocity/Dynamic. Teleport spec pins the no-blast contract.
|
|
202
|
+
- **Stop transition**: a wall that stops must stamp ONE more pass (zeros)
|
|
203
|
+
over its faces — pinned values persist by design, so without it the
|
|
204
|
+
divergence sees phantom inflow forever (`_was_moving` flag; obstacles
|
|
205
|
+
that never move never pay). Piston spec covers move → push → stop.
|
|
206
|
+
- Rotation-induced surface velocity (ω × r) is NOT modelled — a spinner
|
|
207
|
+
reads as a translating wall only (documented on the system).
|
|
208
|
+
- Ordering contract: PhysicsSystem → FluidObstacleSystem → FluidSystem.
|
|
209
|
+
- Gate: scenario 8 (piston slab, faces stamped +4, 10 steps) — mean u ahead
|
|
210
|
+
2.08 baked, wake side 2.60 sign-checked; BC face value asserted == 4
|
|
211
|
+
after 10 steps. Static scenarios all unchanged.
|
|
212
|
+
|
|
213
|
+
## Follow-ups (ordered by leverage)
|
|
214
|
+
|
|
215
|
+
1. **Obstacle voxelization change detection** — static scenes currently pay
|
|
216
|
+
the full clear+rebuild every tick.
|
|
217
|
+
2. **MC + reflection** — only via the second-order advection-reflection
|
|
218
|
+
formulation (Narain et al. 2019, implicit-midpoint form); the naive
|
|
219
|
+
pairing re-injects corrector overshoots (3.7×, with or without clipping).
|
|
220
|
+
3. **bench.spec.js MAC refresh** — the `.skip` suites run, but the
|
|
221
|
+
per-primitive sections still measure the legacy collocated kernels.
|
|
222
|
+
4. **ω × r surface velocity for rotating obstacles** — needs per-cell wall
|
|
223
|
+
velocity instead of one translation vector per obstacle; same stamping
|
|
224
|
+
machinery, finer-grained source.
|
|
96
225
|
|
|
97
226
|
## Review findings driving this (2026-06-12 session)
|
|
98
227
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks an entity's {@link Collider} as a fluid obstacle: each fixed update,
|
|
3
|
+
* {@link FluidObstacleSystem} voxelizes the collider's shape (via its signed
|
|
4
|
+
* distance function, under the entity's rigid `Transform` pose) into the solid
|
|
5
|
+
* mask of every overlapping {@link FluidComponent}, and stamps the entity's
|
|
6
|
+
* {@link RigidBody} velocity onto the wall faces as the moving-wall boundary
|
|
7
|
+
* condition. Wind stops at — and is pushed by — the bodies the physics
|
|
8
|
+
* already knows about; no separate fluid geometry to author.
|
|
9
|
+
*
|
|
10
|
+
* Requirements on the entity (the same tuple {@link PhysicsSystem} uses, so
|
|
11
|
+
* any physical body qualifies as-is):
|
|
12
|
+
* - a `Collider` whose shape implements
|
|
13
|
+
* {@link AbstractShape3D#signed_distance_at_point} (all analytic
|
|
14
|
+
* primitives do; mesh-ish shapes that don't will throw at voxelization),
|
|
15
|
+
* - a `RigidBody` — the wall-velocity source (see the system doc for the
|
|
16
|
+
* exact semantics). Static walls use `BodyKind.Static` as usual.
|
|
17
|
+
* - a `Transform` for the world pose. Following the collider convention,
|
|
18
|
+
* the pose is RIGID — `Transform.scale` is ignored, exactly as the
|
|
19
|
+
* physics narrowphase ignores it.
|
|
20
|
+
*
|
|
21
|
+
* Entities missing any part of the tuple are not obstacles and are skipped —
|
|
22
|
+
* standard ECS tuple semantics, nothing throws.
|
|
23
|
+
*
|
|
24
|
+
* This component is pure data; the behaviour lives on the system.
|
|
25
|
+
*/
|
|
26
|
+
export class FluidObstacle {
|
|
27
|
+
/**
|
|
28
|
+
* Voxelization threshold in WORLD units: a fluid cell becomes solid when
|
|
29
|
+
* the signed distance from its centre to the shape is `<= inflation`.
|
|
30
|
+
*
|
|
31
|
+
* `0` (default) marks exactly the cells whose centres lie inside the
|
|
32
|
+
* shape. Positive values grow the voxel footprint outward — useful for
|
|
33
|
+
* thin obstacles that would otherwise slip between cell centres (a wall
|
|
34
|
+
* thinner than one cell needs `inflation >= cell_size / 2` to voxelize
|
|
35
|
+
* reliably), or to give the fluid a safety margin around fast movers.
|
|
36
|
+
* Negative values shrink it.
|
|
37
|
+
*
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
inflation: number;
|
|
41
|
+
/**
|
|
42
|
+
* True when the previous tick stamped a non-zero wall velocity. A body
|
|
43
|
+
* that stops (or sleeps — the solver zeroes velocities on sleep) must
|
|
44
|
+
* stamp once more with zeros to clear its stale moving BC, because
|
|
45
|
+
* pinned faces preserve their values across mask recomputes by design.
|
|
46
|
+
* System-private.
|
|
47
|
+
* @type {boolean}
|
|
48
|
+
*/
|
|
49
|
+
_was_moving: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* @param {FluidObstacle} other
|
|
52
|
+
* @return {boolean}
|
|
53
|
+
*/
|
|
54
|
+
equals(other: FluidObstacle): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* @return {number}
|
|
57
|
+
*/
|
|
58
|
+
hash(): number;
|
|
59
|
+
toJSON(): {
|
|
60
|
+
inflation: number;
|
|
61
|
+
};
|
|
62
|
+
fromJSON(json: any): void;
|
|
63
|
+
/**
|
|
64
|
+
* @readonly
|
|
65
|
+
* @type {boolean}
|
|
66
|
+
*/
|
|
67
|
+
readonly isFluidObstacle: boolean;
|
|
68
|
+
}
|
|
69
|
+
export namespace FluidObstacle {
|
|
70
|
+
let typeName: string;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=FluidObstacle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FluidObstacle.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/ecs/FluidObstacle.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH;IAEI;;;;;;;;;;;;OAYG;IACH,WAFU,MAAM,CAEF;IAEd;;;;;;;OAOG;IACH,aAFU,OAAO,CAEG;IAEpB;;;OAGG;IACH,cAHW,aAAa,GACZ,OAAO,CAUlB;IAED;;OAEG;IACH,QAFY,MAAM,CAIjB;IAED;;MAIC;IAED,0BAEC;IASL;;;OAGG;IACH,0BAFU,OAAO,CAEsB;CAZtC;;kBAIS,MAAM"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { computeHashFloat } from "../../../../core/primitives/numbers/computeHashFloat.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Marks an entity's {@link Collider} as a fluid obstacle: each fixed update,
|
|
5
|
+
* {@link FluidObstacleSystem} voxelizes the collider's shape (via its signed
|
|
6
|
+
* distance function, under the entity's rigid `Transform` pose) into the solid
|
|
7
|
+
* mask of every overlapping {@link FluidComponent}, and stamps the entity's
|
|
8
|
+
* {@link RigidBody} velocity onto the wall faces as the moving-wall boundary
|
|
9
|
+
* condition. Wind stops at — and is pushed by — the bodies the physics
|
|
10
|
+
* already knows about; no separate fluid geometry to author.
|
|
11
|
+
*
|
|
12
|
+
* Requirements on the entity (the same tuple {@link PhysicsSystem} uses, so
|
|
13
|
+
* any physical body qualifies as-is):
|
|
14
|
+
* - a `Collider` whose shape implements
|
|
15
|
+
* {@link AbstractShape3D#signed_distance_at_point} (all analytic
|
|
16
|
+
* primitives do; mesh-ish shapes that don't will throw at voxelization),
|
|
17
|
+
* - a `RigidBody` — the wall-velocity source (see the system doc for the
|
|
18
|
+
* exact semantics). Static walls use `BodyKind.Static` as usual.
|
|
19
|
+
* - a `Transform` for the world pose. Following the collider convention,
|
|
20
|
+
* the pose is RIGID — `Transform.scale` is ignored, exactly as the
|
|
21
|
+
* physics narrowphase ignores it.
|
|
22
|
+
*
|
|
23
|
+
* Entities missing any part of the tuple are not obstacles and are skipped —
|
|
24
|
+
* standard ECS tuple semantics, nothing throws.
|
|
25
|
+
*
|
|
26
|
+
* This component is pure data; the behaviour lives on the system.
|
|
27
|
+
*/
|
|
28
|
+
export class FluidObstacle {
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Voxelization threshold in WORLD units: a fluid cell becomes solid when
|
|
32
|
+
* the signed distance from its centre to the shape is `<= inflation`.
|
|
33
|
+
*
|
|
34
|
+
* `0` (default) marks exactly the cells whose centres lie inside the
|
|
35
|
+
* shape. Positive values grow the voxel footprint outward — useful for
|
|
36
|
+
* thin obstacles that would otherwise slip between cell centres (a wall
|
|
37
|
+
* thinner than one cell needs `inflation >= cell_size / 2` to voxelize
|
|
38
|
+
* reliably), or to give the fluid a safety margin around fast movers.
|
|
39
|
+
* Negative values shrink it.
|
|
40
|
+
*
|
|
41
|
+
* @type {number}
|
|
42
|
+
*/
|
|
43
|
+
inflation = 0;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* True when the previous tick stamped a non-zero wall velocity. A body
|
|
47
|
+
* that stops (or sleeps — the solver zeroes velocities on sleep) must
|
|
48
|
+
* stamp once more with zeros to clear its stale moving BC, because
|
|
49
|
+
* pinned faces preserve their values across mask recomputes by design.
|
|
50
|
+
* System-private.
|
|
51
|
+
* @type {boolean}
|
|
52
|
+
*/
|
|
53
|
+
_was_moving = false;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {FluidObstacle} other
|
|
57
|
+
* @return {boolean}
|
|
58
|
+
*/
|
|
59
|
+
equals(other) {
|
|
60
|
+
if (other === this) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (other === null || other === undefined || other.isFluidObstacle !== true) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return this.inflation === other.inflation;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @return {number}
|
|
71
|
+
*/
|
|
72
|
+
hash() {
|
|
73
|
+
return computeHashFloat(this.inflation);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
toJSON() {
|
|
77
|
+
return {
|
|
78
|
+
inflation: this.inflation,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fromJSON(json) {
|
|
83
|
+
if (json.inflation !== undefined) this.inflation = json.inflation;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @readonly
|
|
89
|
+
* @type {string}
|
|
90
|
+
*/
|
|
91
|
+
FluidObstacle.typeName = "FluidObstacle";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @readonly
|
|
95
|
+
* @type {boolean}
|
|
96
|
+
*/
|
|
97
|
+
FluidObstacle.prototype.isFluidObstacle = true;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECS system that voxelizes collider geometry into fluid solid masks and
|
|
3
|
+
* stamps rigid-body velocities onto the walls as moving-wall boundary
|
|
4
|
+
* conditions.
|
|
5
|
+
*
|
|
6
|
+
* Every fixed update, each entity carrying ({@link FluidObstacle},
|
|
7
|
+
* {@link Collider}, {@link RigidBody}, {@link Transform}) — the same tuple
|
|
8
|
+
* {@link PhysicsSystem} simulates, plus the marker — is swept against every
|
|
9
|
+
* {@link FluidComponent}: the collider's shape — posed rigidly at the
|
|
10
|
+
* transform's position/rotation via {@link PosedShape3D} — is sampled at the
|
|
11
|
+
* centre of every fluid cell inside its world AABB, and cells whose signed
|
|
12
|
+
* distance is `<= obstacle.inflation` become solid. The fluid then treats
|
|
13
|
+
* those cells exactly like hand-authored walls: faces pinned, pressure
|
|
14
|
+
* Neumann, traces clipped.
|
|
15
|
+
*
|
|
16
|
+
* **Ownership contract**: while at least one obstacle exists, this system
|
|
17
|
+
* OWNS the solid mask of every fluid field — masks are cleared and rebuilt
|
|
18
|
+
* from scratch each tick, so moving obstacles leave no stale walls behind.
|
|
19
|
+
* Hand-written `setSolidAt` state will be wiped; mix the two styles only by
|
|
20
|
+
* expressing the static geometry as obstacle entities too (a `BoxShape3D`
|
|
21
|
+
* collider is cheaper to author than a splat loop anyway). With NO obstacles
|
|
22
|
+
* present the system leaves every mask untouched.
|
|
23
|
+
*
|
|
24
|
+
* **Moving walls — the wall-velocity specification**:
|
|
25
|
+
*
|
|
26
|
+
* - The velocity source is `RigidBody.linearVelocity` (world m/s) — the
|
|
27
|
+
* SAME value the contact solver resolves against, so air and contacts
|
|
28
|
+
* always agree on how fast a wall moves. It is stamped onto every face
|
|
29
|
+
* of every cell the obstacle voxelized (converted to grid units per
|
|
30
|
+
* field); the projection's divergence reads pinned faces at face value,
|
|
31
|
+
* so a moving collider genuinely pushes and drags the air around it.
|
|
32
|
+
* - Pose is still read from `Transform`; velocity is NOT derived from
|
|
33
|
+
* pose deltas. A teleported body (spawn snap, network correction)
|
|
34
|
+
* relocates its walls but stamps no velocity — no one-tick air blast.
|
|
35
|
+
* This mirrors the engine's own contact-solver rule: see
|
|
36
|
+
* {@link BodyKind}.KinematicPosition, deferred for exactly this reason.
|
|
37
|
+
* Consequently pose-animated colliders read as stationary walls that
|
|
38
|
+
* teleport, for air just as for contacts — drive movers through
|
|
39
|
+
* `BodyKind.KinematicVelocity` (or Dynamic) to displace air.
|
|
40
|
+
* - `BodyKind.Static` bodies have zero velocity and stamp nothing.
|
|
41
|
+
* Sleeping bodies stamp nothing either — the solver zeroes velocities
|
|
42
|
+
* on sleep.
|
|
43
|
+
* - A body that stops stamps ONE extra pass of zeros
|
|
44
|
+
* (`FluidObstacle._was_moving`): pinned faces preserve their values
|
|
45
|
+
* across mask recomputes by design, so without it a stale moving BC
|
|
46
|
+
* would drive phantom inflow forever.
|
|
47
|
+
* - A retreating wall leaves its velocity behind on unpinned faces as the
|
|
48
|
+
* wake seed for the fluid that takes its place.
|
|
49
|
+
* - Rotation-induced surface velocity (ω × r from
|
|
50
|
+
* `RigidBody.angularVelocity`) is not modelled yet — fast spinners read
|
|
51
|
+
* as translating walls only (follow-up in REVIEW_02_PLAN.md).
|
|
52
|
+
*
|
|
53
|
+
* **Ordering**: register AFTER {@link PhysicsSystem} (so this tick's solved
|
|
54
|
+
* velocities and integrated poses are read) and BEFORE {@link FluidSystem}
|
|
55
|
+
* (so the simulation step sees this tick's walls and wall velocities).
|
|
56
|
+
* Engine systems run in registration order.
|
|
57
|
+
*
|
|
58
|
+
* Cost: clearing is O(cells) per field; voxelization is one
|
|
59
|
+
* `signed_distance_at_point` per cell inside each obstacle's AABB ∩ field.
|
|
60
|
+
* Static scenes pay the same as moving ones — change detection is a
|
|
61
|
+
* follow-up (REVIEW_02_PLAN.md) if profiling ever points here.
|
|
62
|
+
*/
|
|
63
|
+
export class FluidObstacleSystem extends System<any, any, any, any, any> {
|
|
64
|
+
/**
|
|
65
|
+
* @param {FluidComponent} fluid
|
|
66
|
+
*/
|
|
67
|
+
static "__#143@#refresh_masks"(fluid: FluidComponent): void;
|
|
68
|
+
/**
|
|
69
|
+
* @param {FluidComponent} fluid
|
|
70
|
+
*/
|
|
71
|
+
static "__#143@#clear_field"(fluid: FluidComponent): void;
|
|
72
|
+
/**
|
|
73
|
+
* Mark every cell of `fluid` whose centre lies within `inflation` of the
|
|
74
|
+
* posed shape as solid. Iteration is clipped to the shape's world AABB
|
|
75
|
+
* (grown by `inflation`), so far-away obstacles cost nothing.
|
|
76
|
+
*
|
|
77
|
+
* @param {FluidComponent} fluid
|
|
78
|
+
* @param {PosedShape3D} posed
|
|
79
|
+
* @param {Float64Array} aabb world AABB of the posed shape
|
|
80
|
+
* @param {number} inflation world-units SDF threshold
|
|
81
|
+
* @param {Float64Array} point length-3 scratch
|
|
82
|
+
*/
|
|
83
|
+
static "__#143@#voxelize"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array): void;
|
|
84
|
+
/**
|
|
85
|
+
* Write the obstacle's translation velocity onto every face of every cell
|
|
86
|
+
* it voxelized — the moving-wall boundary condition. Runs AFTER the mask
|
|
87
|
+
* refresh, so the values stick (already-pinned faces keep their stored
|
|
88
|
+
* velocity through subsequent recomputes).
|
|
89
|
+
*
|
|
90
|
+
* Velocity is converted to grid units (cells/second) per field. Where
|
|
91
|
+
* obstacles overlap, the later-visited one wins on shared faces — both
|
|
92
|
+
* claims are walls, the disagreement is sub-cell.
|
|
93
|
+
*
|
|
94
|
+
* @param {FluidComponent} fluid
|
|
95
|
+
* @param {PosedShape3D} posed
|
|
96
|
+
* @param {Float64Array} aabb
|
|
97
|
+
* @param {number} inflation
|
|
98
|
+
* @param {Float64Array} point
|
|
99
|
+
* @param {number} wvx world-units-per-second obstacle velocity
|
|
100
|
+
* @param {number} wvy
|
|
101
|
+
* @param {number} wvz
|
|
102
|
+
*/
|
|
103
|
+
static "__#143@#stamp_wall_velocity"(fluid: FluidComponent, posed: PosedShape3D, aabb: Float64Array, inflation: number, point: Float64Array, wvx: number, wvy: number, wvz: number): void;
|
|
104
|
+
constructor();
|
|
105
|
+
dependencies: (typeof FluidObstacle)[];
|
|
106
|
+
components_used: (ResourceAccessSpecification<typeof RigidBody> | ResourceAccessSpecification<typeof Collider> | ResourceAccessSpecification<typeof Transform> | ResourceAccessSpecification<typeof FluidComponent> | ResourceAccessSpecification<typeof FluidObstacle>)[];
|
|
107
|
+
#private;
|
|
108
|
+
}
|
|
109
|
+
import { System } from "../../../ecs/System.js";
|
|
110
|
+
import { FluidObstacle } from "./FluidObstacle.js";
|
|
111
|
+
import { RigidBody } from "../../ecs/RigidBody.js";
|
|
112
|
+
import { ResourceAccessSpecification } from "../../../../core/model/ResourceAccessSpecification.js";
|
|
113
|
+
import { Collider } from "../../ecs/Collider.js";
|
|
114
|
+
import { Transform } from "../../../ecs/transform/Transform.js";
|
|
115
|
+
import { FluidComponent } from "./FluidComponent.js";
|
|
116
|
+
import { PosedShape3D } from "../../../../core/geom/3d/shape/PosedShape3D.js";
|
|
117
|
+
//# sourceMappingURL=FluidObstacleSystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FluidObstacleSystem.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/ecs/FluidObstacleSystem.js"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AACH;IA+GI;;OAEG;IACH,sCAFW,cAAc,QAIxB;IAED;;OAEG;IACH,oCAFW,cAAc,QAOxB;IAED;;;;;;;;;;OAUG;IACH,iCANW,cAAc,SACd,YAAY,QACZ,YAAY,aACZ,MAAM,SACN,YAAY,QAiDtB;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,4CATW,cAAc,SACd,YAAY,QACZ,YAAY,aACZ,MAAM,SACN,YAAY,OACZ,MAAM,OACN,MAAM,OACN,MAAM,QAqEhB;IAxVe,cAAmB;IAwEnC,uCAA+B;IAE/B,2QAOE;;CAwQL;uBAxVsB,wBAAwB;8BAKjB,oBAAoB;0BAFxB,wBAAwB;4CAJN,uDAAuD;yBAG1E,uBAAuB;0BADtB,qCAAqC;+BAGhC,qBAAqB;6BAPvB,gDAAgD"}
|