@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
|
@@ -3,67 +3,156 @@ import { computeHashFloat } from "../../../../core/primitives/numbers/computeHas
|
|
|
3
3
|
import { AbstractFluidEffector } from "./AbstractFluidEffector.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* The global atmosphere: every fluid face in every field integrates the same
|
|
7
|
+
* linear flow model each step,
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* (as a direction transform — translation ignored) inside {@link apply} to convert
|
|
10
|
-
* the force into the target field's grid-cell units before depositing. The same
|
|
11
|
-
* GlobalFluidEffector applied to fields with different `cell_size` therefore produces
|
|
12
|
-
* the same world-space acceleration on each — gravity stays gravity.
|
|
9
|
+
* dv/dt = force + drag · (wind − v)
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* One effector covers the whole family of "push everything" behaviours —
|
|
12
|
+
* pick the regime by which fields you set:
|
|
13
|
+
*
|
|
14
|
+
* - **`force` alone** — constant body force (gravity, buoyancy). NOTE:
|
|
15
|
+
* incompressible projection cannot oppose a uniform force (a uniform
|
|
16
|
+
* field is divergence-free), so without `drag`, a sealed container, or
|
|
17
|
+
* {@link FluidSimulator#velocity_damping}, the velocity grows without
|
|
18
|
+
* bound. This is the legacy GlobalFluidEffector behaviour.
|
|
19
|
+
*
|
|
20
|
+
* - **`wind` + `drag`** — prevailing wind: the air relaxes toward the wind
|
|
21
|
+
* velocity at `drag` per second; gusts, wakes and splats decay back to
|
|
22
|
+
* the ambient flow. The fixed point is exactly `wind`.
|
|
23
|
+
*
|
|
24
|
+
* - **`force` + `drag` (+ `wind`)** — bounded forcing with a terminal
|
|
25
|
+
* velocity of `wind + force / drag`: falling smoke, drifting ash, rain
|
|
26
|
+
* sheets in a breeze.
|
|
27
|
+
*
|
|
28
|
+
* - **`drag` alone** — pure exponential calming toward still air. (The
|
|
29
|
+
* scene-content twin of {@link FluidSimulator#velocity_damping}, which
|
|
30
|
+
* is the solver-level stability knob that exists even when no effectors
|
|
31
|
+
* are wired.)
|
|
32
|
+
*
|
|
33
|
+
* `wind` only enters through the drag term — with `drag = 0` it is inert by
|
|
34
|
+
* construction (the model degenerates to dv/dt = force).
|
|
35
|
+
*
|
|
36
|
+
* The step uses the EXACT solution of the linear ODE, not an Euler step:
|
|
37
|
+
*
|
|
38
|
+
* v ← v∞ + (v − v∞) · e^(−drag·dt), v∞ = wind + force / drag
|
|
39
|
+
*
|
|
40
|
+
* (and plain `v += force·dt`, also exact, when `drag = 0`). Exactness makes
|
|
41
|
+
* the effector frame-rate independent — two half-steps land exactly where
|
|
42
|
+
* one full step does, including with both terms active.
|
|
43
|
+
*
|
|
44
|
+
* All quantities are WORLD-space: `force` in units/s², `wind` in units/s,
|
|
45
|
+
* `drag` in 1/s. The `world_to_grid` matrix's linear part converts the two
|
|
46
|
+
* vectors at apply time, so one instance produces the same world-space
|
|
47
|
+
* atmosphere on fields of any `cell_size` — gravity stays gravity.
|
|
48
|
+
*
|
|
49
|
+
* Pinned faces (either adjacent cell solid) are skipped — the projection
|
|
50
|
+
* owns those as boundary conditions, and skipping keeps the solid mask
|
|
51
|
+
* authoritative for effectors as well as the solver.
|
|
17
52
|
*/
|
|
18
53
|
export class GlobalFluidEffector extends AbstractFluidEffector {
|
|
19
54
|
|
|
20
55
|
/**
|
|
21
|
-
*
|
|
22
|
-
* Earth gravity in metres-and-seconds).
|
|
23
|
-
* is `world_to_grid · force × dt`.
|
|
56
|
+
* Body-force acceleration in WORLD units per second squared (e.g.
|
|
57
|
+
* `[0, -9.8, 0]` for Earth gravity in metres-and-seconds).
|
|
24
58
|
*
|
|
25
59
|
* @type {[number, number, number]}
|
|
26
60
|
*/
|
|
27
61
|
force = [0, 0, 0];
|
|
28
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Ambient wind velocity in WORLD units per second — the velocity the air
|
|
65
|
+
* relaxes toward. Only effective when {@link drag} is non-zero.
|
|
66
|
+
*
|
|
67
|
+
* @type {[number, number, number]}
|
|
68
|
+
*/
|
|
69
|
+
wind = [0, 0, 0];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Relaxation rate toward the ambient flow, per second. Higher = stiffer
|
|
73
|
+
* atmosphere that re-asserts itself quickly after disturbances; lower =
|
|
74
|
+
* gusts and wakes linger. A disturbance decays to ~37% in `1/drag`
|
|
75
|
+
* seconds. `0` disables the relaxation term entirely.
|
|
76
|
+
*
|
|
77
|
+
* @type {number}
|
|
78
|
+
*/
|
|
79
|
+
drag = 0;
|
|
80
|
+
|
|
29
81
|
/**
|
|
30
82
|
* @param {FluidField} field
|
|
31
83
|
* @param {number} dt
|
|
32
84
|
* @param {Float32Array|number[]} world_to_grid Column-major 4×4 affine; only the
|
|
33
85
|
* linear (upper-left 3×3) part is
|
|
34
|
-
* used since force
|
|
86
|
+
* used since force and wind are
|
|
87
|
+
* directions.
|
|
35
88
|
*/
|
|
36
89
|
apply(field, dt, world_to_grid) {
|
|
90
|
+
const m = world_to_grid;
|
|
91
|
+
|
|
92
|
+
// Direction-transform both vectors into grid units (translation in
|
|
93
|
+
// m[12..14] is for points, not directions). For the axis-aligned
|
|
94
|
+
// uniform-scale matrices built by FluidSystem this collapses to
|
|
95
|
+
// division by cell_size.
|
|
37
96
|
const wfx = this.force[0];
|
|
38
97
|
const wfy = this.force[1];
|
|
39
98
|
const wfz = this.force[2];
|
|
99
|
+
const ax = m[0] * wfx + m[4] * wfy + m[8] * wfz;
|
|
100
|
+
const ay = m[1] * wfx + m[5] * wfy + m[9] * wfz;
|
|
101
|
+
const az = m[2] * wfx + m[6] * wfy + m[10] * wfz;
|
|
40
102
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
103
|
+
const vu = field.velocity_x;
|
|
104
|
+
const vv = field.velocity_y;
|
|
105
|
+
const vw = field.velocity_z;
|
|
106
|
+
const pin_u = field.face_solid_x;
|
|
107
|
+
const pin_v = field.face_solid_y;
|
|
108
|
+
const pin_w = field.face_solid_z;
|
|
109
|
+
const n_u = vu.length;
|
|
110
|
+
const n_v = vv.length;
|
|
111
|
+
const n_w = vw.length;
|
|
44
112
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const fz = (m[2] * wfx + m[6] * wfy + m[10] * wfz) * dt;
|
|
113
|
+
if (this.drag > 0) {
|
|
114
|
+
const wwx = this.wind[0];
|
|
115
|
+
const wwy = this.wind[1];
|
|
116
|
+
const wwz = this.wind[2];
|
|
117
|
+
const gx = m[0] * wwx + m[4] * wwy + m[8] * wwz;
|
|
118
|
+
const gy = m[1] * wwx + m[5] * wwy + m[9] * wwz;
|
|
119
|
+
const gz = m[2] * wwx + m[6] * wwy + m[10] * wwz;
|
|
53
120
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
121
|
+
// Exact step of dv/dt = a + k(w − v): relax toward the terminal
|
|
122
|
+
// velocity v∞ = w + a/k by 1 − e^(−k·dt).
|
|
123
|
+
const inv_drag = 1 / this.drag;
|
|
124
|
+
const tx = gx + ax * inv_drag;
|
|
125
|
+
const ty = gy + ay * inv_drag;
|
|
126
|
+
const tz = gz + az * inv_drag;
|
|
127
|
+
const blend = 1 - Math.exp(-this.drag * dt);
|
|
59
128
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
129
|
+
for (let i = 0; i < n_u; i++) {
|
|
130
|
+
if (pin_u[i] === 0) vu[i] += blend * (tx - vu[i]);
|
|
131
|
+
}
|
|
132
|
+
for (let i = 0; i < n_v; i++) {
|
|
133
|
+
if (pin_v[i] === 0) vv[i] += blend * (ty - vv[i]);
|
|
63
134
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
135
|
+
for (let i = 0; i < n_w; i++) {
|
|
136
|
+
if (pin_w[i] === 0) vw[i] += blend * (tz - vw[i]);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// No drag: pure body force, v += a·dt (exact for constant acceleration).
|
|
142
|
+
const dx = ax * dt;
|
|
143
|
+
const dy = ay * dt;
|
|
144
|
+
const dz = az * dt;
|
|
145
|
+
if (dx === 0 && dy === 0 && dz === 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (let i = 0; i < n_u; i++) {
|
|
149
|
+
if (pin_u[i] === 0) vu[i] += dx;
|
|
150
|
+
}
|
|
151
|
+
for (let i = 0; i < n_v; i++) {
|
|
152
|
+
if (pin_v[i] === 0) vv[i] += dy;
|
|
153
|
+
}
|
|
154
|
+
for (let i = 0; i < n_w; i++) {
|
|
155
|
+
if (pin_w[i] === 0) vw[i] += dz;
|
|
67
156
|
}
|
|
68
157
|
}
|
|
69
158
|
|
|
@@ -78,9 +167,13 @@ export class GlobalFluidEffector extends AbstractFluidEffector {
|
|
|
78
167
|
if (!(other instanceof GlobalFluidEffector)) {
|
|
79
168
|
return false;
|
|
80
169
|
}
|
|
81
|
-
return this.
|
|
170
|
+
return this.drag === other.drag
|
|
171
|
+
&& this.force[0] === other.force[0]
|
|
82
172
|
&& this.force[1] === other.force[1]
|
|
83
|
-
&& this.force[2] === other.force[2]
|
|
173
|
+
&& this.force[2] === other.force[2]
|
|
174
|
+
&& this.wind[0] === other.wind[0]
|
|
175
|
+
&& this.wind[1] === other.wind[1]
|
|
176
|
+
&& this.wind[2] === other.wind[2];
|
|
84
177
|
}
|
|
85
178
|
|
|
86
179
|
/**
|
|
@@ -90,7 +183,11 @@ export class GlobalFluidEffector extends AbstractFluidEffector {
|
|
|
90
183
|
return combine_hash(
|
|
91
184
|
computeHashFloat(this.force[0]),
|
|
92
185
|
computeHashFloat(this.force[1]),
|
|
93
|
-
computeHashFloat(this.force[2])
|
|
186
|
+
computeHashFloat(this.force[2]),
|
|
187
|
+
computeHashFloat(this.wind[0]),
|
|
188
|
+
computeHashFloat(this.wind[1]),
|
|
189
|
+
computeHashFloat(this.wind[2]),
|
|
190
|
+
computeHashFloat(this.drag)
|
|
94
191
|
);
|
|
95
192
|
}
|
|
96
193
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImpulseFluidEffector.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/effector/ImpulseFluidEffector.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;GAiBG;AACH;IAEI;;;OAGG;IACH,UAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEb;IAErB;;;;;;OAMG;IACH,OAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEhB;IAElB;;;;;;OAMG;IACH,QAFU,MAAM,CAEL;
|
|
1
|
+
{"version":3,"file":"ImpulseFluidEffector.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/effector/ImpulseFluidEffector.js"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;GAiBG;AACH;IAEI;;;OAGG;IACH,UAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEb;IAErB;;;;;;OAMG;IACH,OAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEhB;IAElB;;;;;;OAMG;IACH,QAFU,MAAM,CAEL;CA+Md;sCA7PqC,4BAA4B"}
|
|
@@ -93,52 +93,99 @@ export class ImpulseFluidEffector extends AbstractFluidEffector {
|
|
|
93
93
|
const res_x = res[0];
|
|
94
94
|
const res_y = res[1];
|
|
95
95
|
const res_z = res[2];
|
|
96
|
-
const slice_size = res_x * res_y;
|
|
97
|
-
|
|
98
|
-
const x_min = Math.max(0, Math.floor(px - r));
|
|
99
|
-
const x_max = Math.min(res_x - 1, Math.ceil(px + r));
|
|
100
|
-
const y_min = Math.max(0, Math.floor(py - r));
|
|
101
|
-
const y_max = Math.min(res_y - 1, Math.ceil(py + r));
|
|
102
|
-
const z_min = Math.max(0, Math.floor(pz - r));
|
|
103
|
-
const z_max = Math.min(res_z - 1, Math.ceil(pz + r));
|
|
104
|
-
|
|
105
|
-
if (x_min > x_max || y_min > y_max || z_min > z_max) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
96
|
|
|
109
97
|
const r_inv = 1 / r;
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
for (let z = z_min; z <= z_max; z++) {
|
|
117
|
-
const dz = z - pz;
|
|
118
|
-
const z_off = z * slice_size;
|
|
119
|
-
|
|
120
|
-
for (let y = y_min; y <= y_max; y++) {
|
|
121
|
-
const dy = y - py;
|
|
122
|
-
const y_off = y * res_x;
|
|
123
|
-
|
|
124
|
-
for (let x = x_min; x <= x_max; x++) {
|
|
125
|
-
const dx = x - px;
|
|
99
|
+
// Each velocity component deposits onto its own face lattice, with the
|
|
100
|
+
// falloff evaluated at the face position (the lattice is offset by
|
|
101
|
+
// −0.5 on its own axis relative to cell centers). Pinned faces
|
|
102
|
+
// (adjacent solid) are skipped.
|
|
126
103
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
104
|
+
// u (x-faces) — face (x, y, z) sits at (x − 0.5, y, z).
|
|
105
|
+
{
|
|
106
|
+
const vu = field.velocity_x;
|
|
107
|
+
const pin = field.face_solid_x;
|
|
108
|
+
const sx = res_x + 1;
|
|
109
|
+
const x_min = Math.max(0, Math.floor(px + 0.5 - r));
|
|
110
|
+
const x_max = Math.min(res_x, Math.ceil(px + 0.5 + r));
|
|
111
|
+
const y_min = Math.max(0, Math.floor(py - r));
|
|
112
|
+
const y_max = Math.min(res_y - 1, Math.ceil(py + r));
|
|
113
|
+
const z_min = Math.max(0, Math.floor(pz - r));
|
|
114
|
+
const z_max = Math.min(res_z - 1, Math.ceil(pz + r));
|
|
115
|
+
for (let z = z_min; z <= z_max; z++) {
|
|
116
|
+
const dz = z - pz;
|
|
117
|
+
const z_off = z * sx * res_y;
|
|
118
|
+
for (let y = y_min; y <= y_max; y++) {
|
|
119
|
+
const dy = y - py;
|
|
120
|
+
const y_off = z_off + y * sx;
|
|
121
|
+
for (let x = x_min; x <= x_max; x++) {
|
|
122
|
+
const dx = x - 0.5 - px;
|
|
123
|
+
const t = Math.sqrt(dx * dx + dy * dy + dz * dz) * r_inv;
|
|
124
|
+
if (t >= 1) continue;
|
|
125
|
+
const f = y_off + x;
|
|
126
|
+
if (pin[f] !== 0) continue;
|
|
127
|
+
const w = (1 - t) * (1 - t);
|
|
128
|
+
vu[f] += fx * w;
|
|
131
129
|
}
|
|
132
|
-
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
// v (y-faces) — face (x, y, z) sits at (x, y − 0.5, z).
|
|
135
|
+
{
|
|
136
|
+
const vv = field.velocity_y;
|
|
137
|
+
const pin = field.face_solid_y;
|
|
138
|
+
const x_min = Math.max(0, Math.floor(px - r));
|
|
139
|
+
const x_max = Math.min(res_x - 1, Math.ceil(px + r));
|
|
140
|
+
const y_min = Math.max(0, Math.floor(py + 0.5 - r));
|
|
141
|
+
const y_max = Math.min(res_y, Math.ceil(py + 0.5 + r));
|
|
142
|
+
const z_min = Math.max(0, Math.floor(pz - r));
|
|
143
|
+
const z_max = Math.min(res_z - 1, Math.ceil(pz + r));
|
|
144
|
+
for (let z = z_min; z <= z_max; z++) {
|
|
145
|
+
const dz = z - pz;
|
|
146
|
+
const z_off = z * res_x * (res_y + 1);
|
|
147
|
+
for (let y = y_min; y <= y_max; y++) {
|
|
148
|
+
const dy = y - 0.5 - py;
|
|
149
|
+
const y_off = z_off + y * res_x;
|
|
150
|
+
for (let x = x_min; x <= x_max; x++) {
|
|
151
|
+
const dx = x - px;
|
|
152
|
+
const t = Math.sqrt(dx * dx + dy * dy + dz * dz) * r_inv;
|
|
153
|
+
if (t >= 1) continue;
|
|
154
|
+
const f = y_off + x;
|
|
155
|
+
if (pin[f] !== 0) continue;
|
|
156
|
+
const w = (1 - t) * (1 - t);
|
|
157
|
+
vv[f] += fy * w;
|
|
137
158
|
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
138
162
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
163
|
+
// w (z-faces) — face (x, y, z) sits at (x, y, z − 0.5).
|
|
164
|
+
{
|
|
165
|
+
const vw = field.velocity_z;
|
|
166
|
+
const pin = field.face_solid_z;
|
|
167
|
+
const slice = res_x * res_y;
|
|
168
|
+
const x_min = Math.max(0, Math.floor(px - r));
|
|
169
|
+
const x_max = Math.min(res_x - 1, Math.ceil(px + r));
|
|
170
|
+
const y_min = Math.max(0, Math.floor(py - r));
|
|
171
|
+
const y_max = Math.min(res_y - 1, Math.ceil(py + r));
|
|
172
|
+
const z_min = Math.max(0, Math.floor(pz + 0.5 - r));
|
|
173
|
+
const z_max = Math.min(res_z, Math.ceil(pz + 0.5 + r));
|
|
174
|
+
for (let z = z_min; z <= z_max; z++) {
|
|
175
|
+
const dz = z - 0.5 - pz;
|
|
176
|
+
const z_off = z * slice;
|
|
177
|
+
for (let y = y_min; y <= y_max; y++) {
|
|
178
|
+
const dy = y - py;
|
|
179
|
+
const y_off = z_off + y * res_x;
|
|
180
|
+
for (let x = x_min; x <= x_max; x++) {
|
|
181
|
+
const dx = x - px;
|
|
182
|
+
const t = Math.sqrt(dx * dx + dy * dy + dz * dz) * r_inv;
|
|
183
|
+
if (t >= 1) continue;
|
|
184
|
+
const f = y_off + x;
|
|
185
|
+
if (pin[f] !== 0) continue;
|
|
186
|
+
const w = (1 - t) * (1 - t);
|
|
187
|
+
vw[f] += fz * w;
|
|
188
|
+
}
|
|
142
189
|
}
|
|
143
190
|
}
|
|
144
191
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WakeFluidEffector.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/effector/WakeFluidEffector.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH;IAEI;;;;;OAKG;IACH,UAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEb;IAErB;;;;;;OAMG;IACH,mBAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEJ;IAE9B;;;;;OAKG;IACH,QAFU,MAAM,CAEL;IAEX;;;;;OAKG;IACH,UAFU,MAAM,CAEH;IAoBb;;;;;OAKG;IACH,oBAKC;;
|
|
1
|
+
{"version":3,"file":"WakeFluidEffector.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/effector/WakeFluidEffector.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH;IAEI;;;;;OAKG;IACH,UAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEb;IAErB;;;;;;OAMG;IACH,mBAFU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAEJ;IAE9B;;;;;OAKG;IACH,QAFU,MAAM,CAEL;IAEX;;;;;OAKG;IACH,UAFU,MAAM,CAEH;IAoBb;;;;;OAKG;IACH,oBAKC;;CA8RJ;sCAjZqC,4BAA4B"}
|
|
@@ -204,67 +204,121 @@ export class WakeFluidEffector extends AbstractFluidEffector {
|
|
|
204
204
|
const res_x = res[0];
|
|
205
205
|
const res_y = res[1];
|
|
206
206
|
const res_z = res[2];
|
|
207
|
-
const slice_size = res_x * res_y;
|
|
208
207
|
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const x_max = Math.min(res_x - 1, Math.ceil(Math.max(ax, bx) + r));
|
|
212
|
-
const y_min = Math.max(0, Math.floor(Math.min(ay, by) - r));
|
|
213
|
-
const y_max = Math.min(res_y - 1, Math.ceil(Math.max(ay, by) + r));
|
|
214
|
-
const z_min = Math.max(0, Math.floor(Math.min(az, bz) - r));
|
|
215
|
-
const z_max = Math.min(res_z - 1, Math.ceil(Math.max(az, bz) + r));
|
|
208
|
+
const r_inv = 1 / r;
|
|
209
|
+
const inv_seg_len_sq = 1 / seg_len_sq;
|
|
216
210
|
|
|
217
|
-
|
|
211
|
+
// Conservative AABB of the swept tube in cell coordinates; each face
|
|
212
|
+
// lattice clips it to its own index range below.
|
|
213
|
+
const box_x_min = Math.min(ax, bx) - r;
|
|
214
|
+
const box_x_max = Math.max(ax, bx) + r;
|
|
215
|
+
const box_y_min = Math.min(ay, by) - r;
|
|
216
|
+
const box_y_max = Math.max(ay, by) + r;
|
|
217
|
+
const box_z_min = Math.min(az, bz) - r;
|
|
218
|
+
const box_z_max = Math.max(az, bz) + r;
|
|
219
|
+
|
|
220
|
+
if (Math.max(0, Math.floor(box_x_min)) > Math.min(res_x, Math.ceil(box_x_max))
|
|
221
|
+
|| Math.max(0, Math.floor(box_y_min)) > Math.min(res_y, Math.ceil(box_y_max))
|
|
222
|
+
|| Math.max(0, Math.floor(box_z_min)) > Math.min(res_z, Math.ceil(box_z_max))) {
|
|
218
223
|
// Swept tube doesn't intersect the field at all.
|
|
219
224
|
this.#advance_trail_if_manual();
|
|
220
225
|
return;
|
|
221
226
|
}
|
|
222
227
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
228
|
+
// Weight of the (1 − d/R)² falloff at a query position, or 0 outside
|
|
229
|
+
// the tube.
|
|
230
|
+
const tube_weight = (qx, qy, qz) => {
|
|
231
|
+
const dx = qx - ax;
|
|
232
|
+
const dy = qy - ay;
|
|
233
|
+
const dz = qz - az;
|
|
234
|
+
let t = (dx * seg_dx + dy * seg_dy + dz * seg_dz) * inv_seg_len_sq;
|
|
235
|
+
if (t < 0) t = 0;
|
|
236
|
+
else if (t > 1) t = 1;
|
|
237
|
+
const ndx = dx - t * seg_dx;
|
|
238
|
+
const ndy = dy - t * seg_dy;
|
|
239
|
+
const ndz = dz - t * seg_dz;
|
|
240
|
+
const tt = Math.sqrt(ndx * ndx + ndy * ndy + ndz * ndz) * r_inv;
|
|
241
|
+
if (tt >= 1) return 0;
|
|
242
|
+
const fall = 1 - tt;
|
|
243
|
+
return fall * fall;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Each component deposits onto its own face lattice at face positions
|
|
247
|
+
// (offset −0.5 on its own axis). Pinned faces are skipped.
|
|
248
|
+
|
|
249
|
+
// u (x-faces).
|
|
250
|
+
{
|
|
251
|
+
const vu = field.velocity_x;
|
|
252
|
+
const pin = field.face_solid_x;
|
|
253
|
+
const sx = res_x + 1;
|
|
254
|
+
const x_min = Math.max(0, Math.floor(box_x_min + 0.5));
|
|
255
|
+
const x_max = Math.min(res_x, Math.ceil(box_x_max + 0.5));
|
|
256
|
+
const y_min = Math.max(0, Math.floor(box_y_min));
|
|
257
|
+
const y_max = Math.min(res_y - 1, Math.ceil(box_y_max));
|
|
258
|
+
const z_min = Math.max(0, Math.floor(box_z_min));
|
|
259
|
+
const z_max = Math.min(res_z - 1, Math.ceil(box_z_max));
|
|
260
|
+
for (let z = z_min; z <= z_max; z++) {
|
|
261
|
+
const z_off = z * sx * res_y;
|
|
262
|
+
for (let y = y_min; y <= y_max; y++) {
|
|
263
|
+
const y_off = z_off + y * sx;
|
|
264
|
+
for (let x = x_min; x <= x_max; x++) {
|
|
265
|
+
const w = tube_weight(x - 0.5, y, z);
|
|
266
|
+
if (w === 0) continue;
|
|
267
|
+
const f = y_off + x;
|
|
268
|
+
if (pin[f] !== 0) continue;
|
|
269
|
+
vu[f] += impulse_x * w;
|
|
256
270
|
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
257
274
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
275
|
+
// v (y-faces).
|
|
276
|
+
{
|
|
277
|
+
const vv = field.velocity_y;
|
|
278
|
+
const pin = field.face_solid_y;
|
|
279
|
+
const x_min = Math.max(0, Math.floor(box_x_min));
|
|
280
|
+
const x_max = Math.min(res_x - 1, Math.ceil(box_x_max));
|
|
281
|
+
const y_min = Math.max(0, Math.floor(box_y_min + 0.5));
|
|
282
|
+
const y_max = Math.min(res_y, Math.ceil(box_y_max + 0.5));
|
|
283
|
+
const z_min = Math.max(0, Math.floor(box_z_min));
|
|
284
|
+
const z_max = Math.min(res_z - 1, Math.ceil(box_z_max));
|
|
285
|
+
for (let z = z_min; z <= z_max; z++) {
|
|
286
|
+
const z_off = z * res_x * (res_y + 1);
|
|
287
|
+
for (let y = y_min; y <= y_max; y++) {
|
|
288
|
+
const y_off = z_off + y * res_x;
|
|
289
|
+
for (let x = x_min; x <= x_max; x++) {
|
|
290
|
+
const w = tube_weight(x, y - 0.5, z);
|
|
291
|
+
if (w === 0) continue;
|
|
292
|
+
const f = y_off + x;
|
|
293
|
+
if (pin[f] !== 0) continue;
|
|
294
|
+
vv[f] += impulse_y * w;
|
|
263
295
|
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
264
299
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
// w (z-faces).
|
|
301
|
+
{
|
|
302
|
+
const vw = field.velocity_z;
|
|
303
|
+
const pin = field.face_solid_z;
|
|
304
|
+
const slice = res_x * res_y;
|
|
305
|
+
const x_min = Math.max(0, Math.floor(box_x_min));
|
|
306
|
+
const x_max = Math.min(res_x - 1, Math.ceil(box_x_max));
|
|
307
|
+
const y_min = Math.max(0, Math.floor(box_y_min));
|
|
308
|
+
const y_max = Math.min(res_y - 1, Math.ceil(box_y_max));
|
|
309
|
+
const z_min = Math.max(0, Math.floor(box_z_min + 0.5));
|
|
310
|
+
const z_max = Math.min(res_z, Math.ceil(box_z_max + 0.5));
|
|
311
|
+
for (let z = z_min; z <= z_max; z++) {
|
|
312
|
+
const z_off = z * slice;
|
|
313
|
+
for (let y = y_min; y <= y_max; y++) {
|
|
314
|
+
const y_off = z_off + y * res_x;
|
|
315
|
+
for (let x = x_min; x <= x_max; x++) {
|
|
316
|
+
const w = tube_weight(x, y, z - 0.5);
|
|
317
|
+
if (w === 0) continue;
|
|
318
|
+
const f = y_off + x;
|
|
319
|
+
if (pin[f] !== 0) continue;
|
|
320
|
+
vw[f] += impulse_z * w;
|
|
321
|
+
}
|
|
268
322
|
}
|
|
269
323
|
}
|
|
270
324
|
}
|
|
@@ -67,12 +67,35 @@ function main(engine) {
|
|
|
67
67
|
new Entity().add(effectors).build(ecd);
|
|
68
68
|
|
|
69
69
|
// Debug visualisation — a per-frame behavior, separate from the fluid stepping.
|
|
70
|
+
// The MAC velocity lives on three face lattices of different sizes; the
|
|
71
|
+
// visualiser wants cell-resolution channels, so sample to cell centers
|
|
72
|
+
// into cached buffers each frame.
|
|
73
|
+
const cell_count = field.cellCount();
|
|
74
|
+
const view_vx = new Float32Array(cell_count);
|
|
75
|
+
const view_vy = new Float32Array(cell_count);
|
|
76
|
+
const view_vz = new Float32Array(cell_count);
|
|
77
|
+
const sample_scratch = new Float32Array(3);
|
|
78
|
+
|
|
79
|
+
function refresh_velocity_views() {
|
|
80
|
+
for (let z = 0; z < RES_Z; z++) {
|
|
81
|
+
for (let y = 0; y < RES_Y; y++) {
|
|
82
|
+
for (let x = 0; x < RES_X; x++) {
|
|
83
|
+
field.sampleVelocity(sample_scratch, x, y, z);
|
|
84
|
+
const c = field.cellIndex(x, y, z);
|
|
85
|
+
view_vx[c] = sample_scratch[0];
|
|
86
|
+
view_vy[c] = sample_scratch[1];
|
|
87
|
+
view_vz[c] = sample_scratch[2];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
70
93
|
const slice_view = new SliceVisualiser();
|
|
71
94
|
slice_view.size.set(RES_X * 6, RES_Y * 2);
|
|
72
95
|
slice_view.scale.setScalar(2);
|
|
73
96
|
slice_view.transformOrigin.set(0, 0);
|
|
74
97
|
slice_view.setChannels(
|
|
75
|
-
[
|
|
98
|
+
[view_vx, view_vy, view_vz, field.getScalarData("dye")],
|
|
76
99
|
field.getResolution()
|
|
77
100
|
);
|
|
78
101
|
|
|
@@ -81,6 +104,7 @@ function main(engine) {
|
|
|
81
104
|
// FluidSystem.fixedUpdate is what advances the sim; we just observe and
|
|
82
105
|
// time the draw cost here.
|
|
83
106
|
const t0 = performance.now();
|
|
107
|
+
refresh_velocity_views();
|
|
84
108
|
slice_view.draw();
|
|
85
109
|
metrics.get('sim').record(performance.now() - t0);
|
|
86
110
|
}))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trilinearly sample a cell-centered scalar at a fractional position,
|
|
3
|
+
* EXCLUDING solid cells from the interpolation: solid taps contribute zero
|
|
4
|
+
* weight and the remaining weights are renormalized. This is the
|
|
5
|
+
* free-boundary-aware sampling of Bridson §3.x — without it, a back-trace
|
|
6
|
+
* landing NEAR a wall blends the frozen in-wall value into the fluid, which
|
|
7
|
+
* reads as dye bleeding out of (or being eaten by) walls.
|
|
8
|
+
*
|
|
9
|
+
* When every tap is solid (a trace clipped into a fully walled corner) there
|
|
10
|
+
* is nothing valid to interpolate; `fallback` — typically the destination
|
|
11
|
+
* cell's own previous value, i.e. "no transport this step" — is returned.
|
|
12
|
+
*
|
|
13
|
+
* Positions clamp to the cell lattice exactly like
|
|
14
|
+
* {@link scs3d_sample_linear}; with a zero-filled `solid` grid the result
|
|
15
|
+
* matches that sampler up to floating-point reassociation (the weights sum
|
|
16
|
+
* to 1 and renormalization divides by it).
|
|
17
|
+
*
|
|
18
|
+
* @param {Float32Array} data Cell-centered scalar field.
|
|
19
|
+
* @param {Uint8Array} solid Cell-centered solid flags.
|
|
20
|
+
* @param {number} res_x
|
|
21
|
+
* @param {number} res_y
|
|
22
|
+
* @param {number} res_z
|
|
23
|
+
* @param {number} x
|
|
24
|
+
* @param {number} y
|
|
25
|
+
* @param {number} z
|
|
26
|
+
* @param {number} fallback Returned when no fluid tap is available.
|
|
27
|
+
* @return {number}
|
|
28
|
+
*/
|
|
29
|
+
export function v3_grid_sample_scalar_masked(data: Float32Array, solid: Uint8Array, res_x: number, res_y: number, res_z: number, x: number, y: number, z: number, fallback: number): number;
|
|
30
|
+
//# sourceMappingURL=v3_grid_sample_scalar_masked.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v3_grid_sample_scalar_masked.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,mDAXW,YAAY,SACZ,UAAU,SACV,MAAM,SACN,MAAM,SACN,MAAM,KACN,MAAM,KACN,MAAM,KACN,MAAM,YACN,MAAM,GACL,MAAM,CA+DjB"}
|