@woosh/meep-engine 2.48.14 → 2.48.16
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/geom/Quaternion.d.ts +0 -4
- package/src/engine/ecs/systems/RenderSystem.js +4 -1
- package/src/engine/graphics/texture/3d/SingleChannelSampler3D.js +18 -61
- package/src/engine/graphics/texture/3d/scs3d_sample_linear.js +71 -0
- package/src/engine/navigation/ecs/components/Path.d.ts +2 -0
- package/src/engine/physics/fluid/Fluid.js +218 -0
- package/src/engine/physics/fluid/FluidField.js +57 -96
- package/src/engine/physics/fluid/FluidSimulator.js +95 -0
- package/src/engine/physics/fluid/SliceVisualiser.js +165 -0
- package/src/engine/physics/fluid/prototype.js +23 -128
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_forward.js +62 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_advection_reverse.js +8 -0
- package/src/engine/physics/fluid/solver/v3_grid_apply_diffusion.js +31 -16
package/package.json
CHANGED
|
@@ -34,12 +34,8 @@ export default class Quaternion {
|
|
|
34
34
|
|
|
35
35
|
normalize(): void
|
|
36
36
|
|
|
37
|
-
__setThreeEuler(threeEuler: any): void
|
|
38
|
-
|
|
39
37
|
__setFromEuler(x: number, y: number, z: number, order?: string): void
|
|
40
38
|
|
|
41
|
-
threeSetRotationMatrix(result: Matrix4): void
|
|
42
|
-
|
|
43
39
|
setFromRotationMatrix(m: Matrix4): void
|
|
44
40
|
|
|
45
41
|
decodeFromUint32(uint: number): void
|
|
@@ -138,7 +138,10 @@ class RenderSystem extends System {
|
|
|
138
138
|
|
|
139
139
|
function handleRotationChange() {
|
|
140
140
|
const m = renderable.object;
|
|
141
|
-
|
|
141
|
+
const euler = m.rotation;
|
|
142
|
+
|
|
143
|
+
transform.rotation.__setFromEuler(euler.x, euler.y, euler.z, euler.order);
|
|
144
|
+
|
|
142
145
|
updateMeshTransform(renderable);
|
|
143
146
|
updateNodeByTransformAndBBB(renderable.bvh, renderable.boundingBox, transform);
|
|
144
147
|
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { lerp } from "../../../../core/math/lerp.js";
|
|
1
|
+
import { scs3d_sample_linear } from "./scs3d_sample_linear.js";
|
|
3
2
|
|
|
4
3
|
export class SingleChannelSampler3D {
|
|
5
4
|
|
|
6
5
|
data = new Float32Array(0)
|
|
7
|
-
#resolution = [0, 0, 0]
|
|
6
|
+
#resolution = new Uint32Array([0, 0, 0])
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param {Float32Array} data
|
|
11
|
+
* @param {ArrayLike<number>|Uint32Array|number[]} resolution
|
|
12
|
+
* @return {SingleChannelSampler3D}
|
|
13
|
+
*/
|
|
14
|
+
static from(data, resolution) {
|
|
15
|
+
const r = new SingleChannelSampler3D();
|
|
16
|
+
|
|
17
|
+
r.data = data;
|
|
18
|
+
r.#resolution.set(resolution);
|
|
19
|
+
|
|
20
|
+
return r;
|
|
21
|
+
}
|
|
8
22
|
|
|
9
23
|
set resolution(v) {
|
|
10
24
|
const width = v[0];
|
|
@@ -66,64 +80,7 @@ export class SingleChannelSampler3D {
|
|
|
66
80
|
const res_x = resolution[0];
|
|
67
81
|
const res_y = resolution[1];
|
|
68
82
|
const res_z = resolution[2];
|
|
69
|
-
|
|
70
|
-
const x_max = res_x - 1;
|
|
71
|
-
const y_max = res_y - 1;
|
|
72
|
-
const z_max = res_z - 1;
|
|
73
|
-
|
|
74
|
-
const x_clamped = clamp(x, 0, x_max);
|
|
75
|
-
const y_clamped = clamp(y, 0, y_max);
|
|
76
|
-
const z_clamped = clamp(z, 0, z_max);
|
|
77
|
-
|
|
78
|
-
const x0 = x_clamped | 0;
|
|
79
|
-
const y0 = y_clamped | 0;
|
|
80
|
-
const z0 = z_clamped | 0;
|
|
81
|
-
|
|
82
|
-
const f_x = x_clamped - x0;
|
|
83
|
-
const f_y = y_clamped - y0;
|
|
84
|
-
const f_z = z_clamped - z0;
|
|
85
|
-
|
|
86
|
-
const x1 = x_clamped === x0 ? x0 : x0 + 1;
|
|
87
|
-
const y1 = y_clamped === y0 ? y0 : y0 + 1;
|
|
88
|
-
const z1 = z_clamped === z0 ? z0 : z0 + 1;
|
|
89
|
-
|
|
90
|
-
// get 8 points
|
|
91
|
-
const data = this.data;
|
|
92
|
-
|
|
93
|
-
const z0_offset = z0 * (res_x * res_y);
|
|
94
|
-
|
|
95
|
-
const y0_offset = y0 * res_x;
|
|
96
|
-
|
|
97
|
-
const x0_offset = x0;
|
|
98
|
-
const x1_offset = x1;
|
|
99
|
-
|
|
100
|
-
const x0y0z0 = data[z0_offset + y0_offset + x0_offset];
|
|
101
|
-
const x1y0z0 = data[z0_offset + y0_offset + x1_offset];
|
|
102
|
-
|
|
103
|
-
const y1_offset = y1 * res_x;
|
|
104
|
-
|
|
105
|
-
const x0y1z0 = data[z0_offset + y1_offset + x0_offset];
|
|
106
|
-
const x1y1z0 = data[z0_offset + y1_offset + x1_offset];
|
|
107
|
-
|
|
108
|
-
const z1_offset = z1 * (res_x * res_y);
|
|
109
|
-
|
|
110
|
-
const x0y0z1 = data[z1_offset + y0_offset + x0_offset];
|
|
111
|
-
const x1y0z1 = data[z1_offset + y0_offset + x1_offset];
|
|
112
|
-
|
|
113
|
-
const x0y1z1 = data[z1_offset + y1_offset + x0_offset];
|
|
114
|
-
const x1y1z1 = data[z1_offset + y1_offset + x1_offset];
|
|
115
|
-
|
|
116
|
-
// filter on z0
|
|
117
|
-
const lerp_z0_y0 = lerp(x0y0z0, x1y0z0, f_x);
|
|
118
|
-
const lerp_z0_y1 = lerp(x0y1z0, x1y1z0, f_x);
|
|
119
|
-
const lerp_z0 = lerp(lerp_z0_y0, lerp_z0_y1, f_y);
|
|
120
|
-
|
|
121
|
-
// filter on z1
|
|
122
|
-
const lerp_z1_y0 = lerp(x0y0z1, x1y0z1, f_x);
|
|
123
|
-
const lerp_z1_y1 = lerp(x0y1z1, x1y1z1, f_x);
|
|
124
|
-
const lerp_z1 = lerp(lerp_z1_y0, lerp_z1_y1, f_y);
|
|
125
|
-
|
|
126
|
-
return lerp(lerp_z0, lerp_z1, f_z);
|
|
83
|
+
return scs3d_sample_linear(this.data, res_x, res_y, res_z, x, y, z);
|
|
127
84
|
}
|
|
128
85
|
|
|
129
86
|
/**
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { clamp } from "../../../../core/math/clamp.js";
|
|
2
|
+
import { lerp } from "../../../../core/math/lerp.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param {Float32Array} data
|
|
7
|
+
* @param {number} res_x
|
|
8
|
+
* @param {number} res_y
|
|
9
|
+
* @param {number} res_z
|
|
10
|
+
* @param {number} x
|
|
11
|
+
* @param {number} y
|
|
12
|
+
* @param {number} z
|
|
13
|
+
* @return {number}
|
|
14
|
+
*/
|
|
15
|
+
export function scs3d_sample_linear(data, res_x, res_y, res_z, x, y, z) {
|
|
16
|
+
const x_max = res_x - 1;
|
|
17
|
+
const y_max = res_y - 1;
|
|
18
|
+
const z_max = res_z - 1;
|
|
19
|
+
|
|
20
|
+
const x_clamped = clamp(x, 0, x_max);
|
|
21
|
+
const y_clamped = clamp(y, 0, y_max);
|
|
22
|
+
const z_clamped = clamp(z, 0, z_max);
|
|
23
|
+
|
|
24
|
+
const x0 = x_clamped | 0;
|
|
25
|
+
const y0 = y_clamped | 0;
|
|
26
|
+
const z0 = z_clamped | 0;
|
|
27
|
+
|
|
28
|
+
const f_x = x_clamped - x0;
|
|
29
|
+
const f_y = y_clamped - y0;
|
|
30
|
+
const f_z = z_clamped - z0;
|
|
31
|
+
|
|
32
|
+
const x1 = x_clamped === x0 ? x0 : x0 + 1;
|
|
33
|
+
const y1 = y_clamped === y0 ? y0 : y0 + 1;
|
|
34
|
+
const z1 = z_clamped === z0 ? z0 : z0 + 1;
|
|
35
|
+
|
|
36
|
+
// get 8 points
|
|
37
|
+
const z0_offset = z0 * (res_x * res_y);
|
|
38
|
+
|
|
39
|
+
const y0_offset = y0 * res_x;
|
|
40
|
+
|
|
41
|
+
const x0_offset = x0;
|
|
42
|
+
const x1_offset = x1;
|
|
43
|
+
|
|
44
|
+
const x0y0z0 = data[z0_offset + y0_offset + x0_offset];
|
|
45
|
+
const x1y0z0 = data[z0_offset + y0_offset + x1_offset];
|
|
46
|
+
|
|
47
|
+
const y1_offset = y1 * res_x;
|
|
48
|
+
|
|
49
|
+
const x0y1z0 = data[z0_offset + y1_offset + x0_offset];
|
|
50
|
+
const x1y1z0 = data[z0_offset + y1_offset + x1_offset];
|
|
51
|
+
|
|
52
|
+
const z1_offset = z1 * (res_x * res_y);
|
|
53
|
+
|
|
54
|
+
const x0y0z1 = data[z1_offset + y0_offset + x0_offset];
|
|
55
|
+
const x1y0z1 = data[z1_offset + y0_offset + x1_offset];
|
|
56
|
+
|
|
57
|
+
const x0y1z1 = data[z1_offset + y1_offset + x0_offset];
|
|
58
|
+
const x1y1z1 = data[z1_offset + y1_offset + x1_offset];
|
|
59
|
+
|
|
60
|
+
// filter on z0
|
|
61
|
+
const lerp_z0_y0 = lerp(x0y0z0, x1y0z0, f_x);
|
|
62
|
+
const lerp_z0_y1 = lerp(x0y1z0, x1y1z0, f_x);
|
|
63
|
+
const lerp_z0 = lerp(lerp_z0_y0, lerp_z0_y1, f_y);
|
|
64
|
+
|
|
65
|
+
// filter on z1
|
|
66
|
+
const lerp_z1_y0 = lerp(x0y0z1, x1y0z1, f_x);
|
|
67
|
+
const lerp_z1_y1 = lerp(x0y1z1, x1y1z1, f_x);
|
|
68
|
+
const lerp_z1 = lerp(lerp_z1_y0, lerp_z1_y1, f_y);
|
|
69
|
+
|
|
70
|
+
return lerp(lerp_z0, lerp_z1, f_z);
|
|
71
|
+
}
|
|
@@ -33,6 +33,8 @@ export default class Path {
|
|
|
33
33
|
|
|
34
34
|
public sample(result: Vector3, offset: number, interpolation?: InterpolationType): boolean
|
|
35
35
|
|
|
36
|
+
find_index_and_normalized_distance(result:ArrayLike<number>, absolute_distance:number):boolean
|
|
37
|
+
|
|
36
38
|
public copy(other: Path): void
|
|
37
39
|
|
|
38
40
|
public clone(): Path
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export class Fluid {
|
|
2
|
+
constructor(density, numX, numY, h) {
|
|
3
|
+
this.density = density;
|
|
4
|
+
this.numX = numX + 2;
|
|
5
|
+
this.numY = numY + 2;
|
|
6
|
+
this.numCells = this.numX * this.numY;
|
|
7
|
+
this.h = h;
|
|
8
|
+
this.u = new Float32Array(this.numCells);
|
|
9
|
+
this.v = new Float32Array(this.numCells);
|
|
10
|
+
this.newU = new Float32Array(this.numCells);
|
|
11
|
+
this.newV = new Float32Array(this.numCells);
|
|
12
|
+
this.p = new Float32Array(this.numCells);
|
|
13
|
+
this.s = new Float32Array(this.numCells);
|
|
14
|
+
this.m = new Float32Array(this.numCells);
|
|
15
|
+
this.newM = new Float32Array(this.numCells);
|
|
16
|
+
this.m.fill(1.0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
integrate(dt, gravity) {
|
|
20
|
+
var n = this.numY;
|
|
21
|
+
for (var i = 1; i < this.numX; i++) {
|
|
22
|
+
for (var j = 1; j < this.numY-1; j++) {
|
|
23
|
+
if (this.s[i*n + j] != 0.0 && this.s[i*n + j-1] != 0.0)
|
|
24
|
+
this.v[i*n + j] += gravity * dt;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
solveIncompressibility(numIters, dt) {
|
|
30
|
+
|
|
31
|
+
var n = this.numY;
|
|
32
|
+
var cp = this.density * this.h / dt;
|
|
33
|
+
|
|
34
|
+
for (var iter = 0; iter < numIters; iter++) {
|
|
35
|
+
|
|
36
|
+
for (var i = 1; i < this.numX-1; i++) {
|
|
37
|
+
for (var j = 1; j < this.numY-1; j++) {
|
|
38
|
+
|
|
39
|
+
if (this.s[i*n + j] == 0.0)
|
|
40
|
+
continue;
|
|
41
|
+
|
|
42
|
+
var s = this.s[i*n + j];
|
|
43
|
+
var sx0 = this.s[(i-1)*n + j];
|
|
44
|
+
var sx1 = this.s[(i+1)*n + j];
|
|
45
|
+
var sy0 = this.s[i*n + j-1];
|
|
46
|
+
var sy1 = this.s[i*n + j+1];
|
|
47
|
+
var s = sx0 + sx1 + sy0 + sy1;
|
|
48
|
+
if (s == 0.0)
|
|
49
|
+
continue;
|
|
50
|
+
|
|
51
|
+
var div = this.u[(i+1)*n + j] - this.u[i*n + j] +
|
|
52
|
+
this.v[i*n + j+1] - this.v[i*n + j];
|
|
53
|
+
|
|
54
|
+
var p = -div / s;
|
|
55
|
+
p *= scene.overRelaxation;
|
|
56
|
+
this.p[i*n + j] += cp * p;
|
|
57
|
+
|
|
58
|
+
this.u[i*n + j] -= sx0 * p;
|
|
59
|
+
this.u[(i+1)*n + j] += sx1 * p;
|
|
60
|
+
this.v[i*n + j] -= sy0 * p;
|
|
61
|
+
this.v[i*n + j+1] += sy1 * p;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
extrapolate() {
|
|
68
|
+
var n = this.numY;
|
|
69
|
+
for (var i = 0; i < this.numX; i++) {
|
|
70
|
+
this.u[i*n + 0] = this.u[i*n + 1];
|
|
71
|
+
this.u[i*n + this.numY-1] = this.u[i*n + this.numY-2];
|
|
72
|
+
}
|
|
73
|
+
for (var j = 0; j < this.numY; j++) {
|
|
74
|
+
this.v[0*n + j] = this.v[1*n + j];
|
|
75
|
+
this.v[(this.numX-1)*n + j] = this.v[(this.numX-2)*n + j]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
sampleField(x, y, field) {
|
|
80
|
+
var n = this.numY;
|
|
81
|
+
var h = this.h;
|
|
82
|
+
var h1 = 1.0 / h;
|
|
83
|
+
var h2 = 0.5 * h;
|
|
84
|
+
|
|
85
|
+
x = Math.max(Math.min(x, this.numX * h), h);
|
|
86
|
+
y = Math.max(Math.min(y, this.numY * h), h);
|
|
87
|
+
|
|
88
|
+
var dx = 0.0;
|
|
89
|
+
var dy = 0.0;
|
|
90
|
+
|
|
91
|
+
var f;
|
|
92
|
+
|
|
93
|
+
switch (field) {
|
|
94
|
+
case U_FIELD: f = this.u; dy = h2; break;
|
|
95
|
+
case V_FIELD: f = this.v; dx = h2; break;
|
|
96
|
+
case S_FIELD: f = this.m; dx = h2; dy = h2; break
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var x0 = Math.min(Math.floor((x-dx)*h1), this.numX-1);
|
|
101
|
+
var tx = ((x-dx) - x0*h) * h1;
|
|
102
|
+
var x1 = Math.min(x0 + 1, this.numX-1);
|
|
103
|
+
|
|
104
|
+
var y0 = Math.min(Math.floor((y-dy)*h1), this.numY-1);
|
|
105
|
+
var ty = ((y-dy) - y0*h) * h1;
|
|
106
|
+
var y1 = Math.min(y0 + 1, this.numY-1);
|
|
107
|
+
|
|
108
|
+
var sx = 1.0 - tx;
|
|
109
|
+
var sy = 1.0 - ty;
|
|
110
|
+
|
|
111
|
+
var val = sx*sy * f[x0*n + y0] +
|
|
112
|
+
tx*sy * f[x1*n + y0] +
|
|
113
|
+
tx*ty * f[x1*n + y1] +
|
|
114
|
+
sx*ty * f[x0*n + y1];
|
|
115
|
+
|
|
116
|
+
return val;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
avgU(i, j) {
|
|
120
|
+
var n = this.numY;
|
|
121
|
+
var u = (this.u[i*n + j-1] + this.u[i*n + j] +
|
|
122
|
+
this.u[(i+1)*n + j-1] + this.u[(i+1)*n + j]) * 0.25;
|
|
123
|
+
return u;
|
|
124
|
+
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
avgV(i, j) {
|
|
128
|
+
var n = this.numY;
|
|
129
|
+
var v = (this.v[(i-1)*n + j] + this.v[i*n + j] +
|
|
130
|
+
this.v[(i-1)*n + j+1] + this.v[i*n + j+1]) * 0.25;
|
|
131
|
+
return v;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
advectVel(dt) {
|
|
135
|
+
|
|
136
|
+
this.newU.set(this.u);
|
|
137
|
+
this.newV.set(this.v);
|
|
138
|
+
|
|
139
|
+
var n = this.numY;
|
|
140
|
+
var h = this.h;
|
|
141
|
+
var h2 = 0.5 * h;
|
|
142
|
+
|
|
143
|
+
for (var i = 1; i < this.numX; i++) {
|
|
144
|
+
for (var j = 1; j < this.numY; j++) {
|
|
145
|
+
|
|
146
|
+
cnt++;
|
|
147
|
+
|
|
148
|
+
// u component
|
|
149
|
+
if (this.s[i*n + j] != 0.0 && this.s[(i-1)*n + j] != 0.0 && j < this.numY - 1) {
|
|
150
|
+
var x = i*h;
|
|
151
|
+
var y = j*h + h2;
|
|
152
|
+
var u = this.u[i*n + j];
|
|
153
|
+
var v = this.avgV(i, j);
|
|
154
|
+
// var v = this.sampleField(x,y, V_FIELD);
|
|
155
|
+
x = x - dt*u;
|
|
156
|
+
y = y - dt*v;
|
|
157
|
+
u = this.sampleField(x,y, U_FIELD);
|
|
158
|
+
this.newU[i*n + j] = u;
|
|
159
|
+
}
|
|
160
|
+
// v component
|
|
161
|
+
if (this.s[i*n + j] != 0.0 && this.s[i*n + j-1] != 0.0 && i < this.numX - 1) {
|
|
162
|
+
var x = i*h + h2;
|
|
163
|
+
var y = j*h;
|
|
164
|
+
var u = this.avgU(i, j);
|
|
165
|
+
// var u = this.sampleField(x,y, U_FIELD);
|
|
166
|
+
var v = this.v[i*n + j];
|
|
167
|
+
x = x - dt*u;
|
|
168
|
+
y = y - dt*v;
|
|
169
|
+
v = this.sampleField(x,y, V_FIELD);
|
|
170
|
+
this.newV[i*n + j] = v;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.u.set(this.newU);
|
|
176
|
+
this.v.set(this.newV);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
advectSmoke(dt) {
|
|
180
|
+
|
|
181
|
+
this.newM.set(this.m);
|
|
182
|
+
|
|
183
|
+
var n = this.numY;
|
|
184
|
+
var h = this.h;
|
|
185
|
+
var h2 = 0.5 * h;
|
|
186
|
+
|
|
187
|
+
for (var i = 1; i < this.numX-1; i++) {
|
|
188
|
+
for (var j = 1; j < this.numY-1; j++) {
|
|
189
|
+
|
|
190
|
+
if (this.s[i*n + j] != 0.0) {
|
|
191
|
+
var u = (this.u[i*n + j] + this.u[(i+1)*n + j]) * 0.5;
|
|
192
|
+
var v = (this.v[i*n + j] + this.v[i*n + j+1]) * 0.5;
|
|
193
|
+
var x = i*h + h2 - dt*u;
|
|
194
|
+
var y = j*h + h2 - dt*v;
|
|
195
|
+
|
|
196
|
+
this.newM[i*n + j] = this.sampleField(x,y, S_FIELD);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
this.m.set(this.newM);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ----------------- end of simulator ------------------------------
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
simulate(dt, gravity, numIters) {
|
|
207
|
+
|
|
208
|
+
this.integrate(dt, gravity);
|
|
209
|
+
|
|
210
|
+
this.p.fill(0.0);
|
|
211
|
+
this.solveIncompressibility(numIters, dt);
|
|
212
|
+
|
|
213
|
+
this.extrapolate();
|
|
214
|
+
this.advectVel(dt);
|
|
215
|
+
this.advectSmoke(dt);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import { clamp } from "../../../core/math/clamp.js";
|
|
2
|
-
import { lerp } from "../../../core/math/lerp.js";
|
|
3
1
|
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
* @param {Float32Array} data
|
|
7
|
-
* @param {number[]} resolution
|
|
8
|
-
*/
|
|
9
|
-
function v3_grid_apply_advection_forward(data, resolution) {
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {Float32Array} data
|
|
16
|
-
* @param {number[]} resolution
|
|
17
|
-
*/
|
|
18
|
-
function v3_grid_apply_advection_reverse(data, resolution) {
|
|
19
2
|
|
|
3
|
+
class FluidAttribute {
|
|
4
|
+
name = "unnamed"
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @type {Float32Array}
|
|
8
|
+
*/
|
|
9
|
+
data = null
|
|
20
10
|
}
|
|
21
11
|
|
|
22
12
|
/**
|
|
@@ -26,117 +16,84 @@ function v3_grid_apply_advection_reverse(data, resolution) {
|
|
|
26
16
|
* @see Inspired by GDC talk "Interactive Wind and Vegetation in 'God of War'" - https://www.youtube.com/watch?v=MKX45_riWQA
|
|
27
17
|
*/
|
|
28
18
|
export class FluidField {
|
|
29
|
-
|
|
19
|
+
|
|
20
|
+
#attributes = [];
|
|
21
|
+
|
|
22
|
+
|
|
30
23
|
#size = [0, 0, 0]
|
|
31
24
|
#resolution = [0, 0, 0]
|
|
32
25
|
|
|
26
|
+
buffer = null;
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
38
|
-
}
|
|
28
|
+
addAttribute(name) {
|
|
29
|
+
const attribute = new FluidAttribute();
|
|
30
|
+
attribute.name = name;
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* @param {number} u
|
|
44
|
-
* @param {number} v
|
|
45
|
-
* @param {number} w
|
|
46
|
-
*/
|
|
47
|
-
sample_linear_uv(out, u, v, w) {
|
|
48
|
-
const max_x = this.#resolution[0] - 1;
|
|
49
|
-
const max_y = this.#resolution[1] - 1;
|
|
50
|
-
const max_z = this.#resolution[2] - 1;
|
|
32
|
+
const index = this.#attributes.length;
|
|
33
|
+
|
|
34
|
+
this.#attributes.push(attribute);
|
|
51
35
|
|
|
52
|
-
|
|
36
|
+
return index;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#findAttribute(name) {
|
|
40
|
+
return this.#attributes.find(a => a.name === name);
|
|
53
41
|
}
|
|
54
42
|
|
|
55
43
|
/**
|
|
56
44
|
*
|
|
57
|
-
* @param {
|
|
58
|
-
* @
|
|
59
|
-
* @param {number} y
|
|
60
|
-
* @param {number} z
|
|
45
|
+
* @param {string} name
|
|
46
|
+
* @return {Float32Array}
|
|
61
47
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
48
|
+
getAttributeData(name) {
|
|
49
|
+
return this.#findAttribute(name)?.data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getAttributeCount() {
|
|
53
|
+
return this.#attributes.length;
|
|
66
54
|
}
|
|
67
55
|
|
|
68
56
|
/**
|
|
69
57
|
*
|
|
70
|
-
* @param {
|
|
71
|
-
* @param {number}
|
|
72
|
-
* @param {number} z
|
|
73
|
-
* @param {number} channel
|
|
74
|
-
* @returns {number}
|
|
58
|
+
* @param {ArrayBuffer} buffer
|
|
59
|
+
* @param {number} offset
|
|
75
60
|
*/
|
|
76
|
-
|
|
77
|
-
const resolution = this.#resolution;
|
|
61
|
+
attachBuffer(buffer, offset = 0) {
|
|
78
62
|
|
|
79
|
-
const
|
|
80
|
-
const res_y = resolution[1];
|
|
81
|
-
const res_z = resolution[2];
|
|
63
|
+
const attribute_count = this.#attributes.length;
|
|
82
64
|
|
|
83
|
-
const
|
|
84
|
-
const y_max = res_y - 1;
|
|
85
|
-
const z_max = res_z - 1;
|
|
65
|
+
const cell_count = this.#resolution[0] * this.#resolution[1] * this.#resolution[2];
|
|
86
66
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const z_clamped = clamp(z, 0, z_max);
|
|
67
|
+
for (let i = 0; i < attribute_count; i++) {
|
|
68
|
+
const attribute = this.#attributes[i];
|
|
90
69
|
|
|
91
|
-
|
|
92
|
-
const y0 = y_clamped | 0;
|
|
93
|
-
const z0 = z_clamped | 0;
|
|
70
|
+
const array = new Float32Array(buffer, offset + i * cell_count * 4, cell_count);
|
|
94
71
|
|
|
95
|
-
const f_x = x_clamped - x0;
|
|
96
|
-
const f_y = y_clamped - y0;
|
|
97
|
-
const f_z = z_clamped - z0;
|
|
98
72
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const z1 = z_clamped === z0 ? z0 : z0 + 1;
|
|
73
|
+
attribute.data = array;
|
|
74
|
+
}
|
|
102
75
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const z0_offset = z0 * (res_x * res_y) * 3;
|
|
107
|
-
|
|
108
|
-
const y0_offset = y0 * res_x * 3;
|
|
109
|
-
|
|
110
|
-
const x0_offset = x0 * 3 + channel;
|
|
111
|
-
const x1_offset = x1 * 3 + channel;
|
|
76
|
+
this.buffer = buffer;
|
|
77
|
+
}
|
|
112
78
|
|
|
113
|
-
|
|
114
|
-
const x1y0z0 = data[z0_offset + y0_offset + x1_offset];
|
|
79
|
+
setAttributeAt(attribute_index, x, y, z, value) {
|
|
115
80
|
|
|
116
|
-
const
|
|
81
|
+
const attribute = this.#attributes[attribute_index];
|
|
117
82
|
|
|
118
|
-
const
|
|
119
|
-
const x1y1z0 = data[z0_offset + y1_offset + x1_offset];
|
|
83
|
+
const data = attribute.data;
|
|
120
84
|
|
|
121
|
-
|
|
85
|
+
data[z * this.#resolution[0] * this.#resolution[1] + y * this.#resolution[0] + x] = value;
|
|
122
86
|
|
|
123
|
-
|
|
124
|
-
const x1y0z1 = data[z1_offset + y0_offset + x1_offset];
|
|
87
|
+
}
|
|
125
88
|
|
|
126
|
-
|
|
127
|
-
const
|
|
89
|
+
build() {
|
|
90
|
+
const attribute_count = this.#attributes.length;
|
|
128
91
|
|
|
129
|
-
|
|
130
|
-
const lerp_z0_y0 = lerp(x0y0z0, x1y0z0, f_x);
|
|
131
|
-
const lerp_z0_y1 = lerp(x0y1z0, x1y1z0, f_x);
|
|
132
|
-
const lerp_z0 = lerp(lerp_z0_y0, lerp_z0_y1, f_y);
|
|
92
|
+
const cell_count = this.#resolution[0] * this.#resolution[1] * this.#resolution[2];
|
|
133
93
|
|
|
134
|
-
|
|
135
|
-
const lerp_z1_y0 = lerp(x0y0z1, x1y0z1, f_x);
|
|
136
|
-
const lerp_z1_y1 = lerp(x0y1z1, x1y1z1, f_x);
|
|
137
|
-
const lerp_z1 = lerp(lerp_z1_y0, lerp_z1_y1, f_y);
|
|
94
|
+
const data_buffer = new ArrayBuffer(cell_count * attribute_count * 4);
|
|
138
95
|
|
|
139
|
-
|
|
96
|
+
this.attachBuffer(data_buffer);
|
|
140
97
|
}
|
|
141
98
|
|
|
142
99
|
set size(v) {
|
|
@@ -158,4 +115,8 @@ export class FluidField {
|
|
|
158
115
|
this.#resolution[1] = height;
|
|
159
116
|
this.#resolution[2] = depth
|
|
160
117
|
}
|
|
118
|
+
|
|
119
|
+
get resolution() {
|
|
120
|
+
return this.#resolution;
|
|
121
|
+
}
|
|
161
122
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { v3_grid_apply_diffusion } from "./solver/v3_grid_apply_diffusion.js";
|
|
2
|
+
import { v3_grid_apply_advection_forward } from "./solver/v3_grid_apply_advection_forward.js";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class FluidSimulator {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @type {ArrayBuffer|null}
|
|
10
|
+
*/
|
|
11
|
+
buffer = null;
|
|
12
|
+
|
|
13
|
+
ensure_buffer(size) {
|
|
14
|
+
if (this.buffer === null || this.buffer.byteLength < size) {
|
|
15
|
+
this.buffer = new ArrayBuffer(size);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {FluidField} field
|
|
22
|
+
* @param {number} time_delta_in_seconds
|
|
23
|
+
*/
|
|
24
|
+
step(field, time_delta_in_seconds) {
|
|
25
|
+
// create buffer big enough to store all attribute twice
|
|
26
|
+
const resolution = field.resolution;
|
|
27
|
+
|
|
28
|
+
const attribute_cell_count = resolution[0] * resolution[1] * resolution[2];
|
|
29
|
+
const attribute_byte_size = attribute_cell_count * 4;
|
|
30
|
+
const field_byte_size = field.getAttributeCount() * attribute_byte_size;
|
|
31
|
+
|
|
32
|
+
this.ensure_buffer(field_byte_size * 2);
|
|
33
|
+
|
|
34
|
+
const sim_buffer = this.buffer;
|
|
35
|
+
|
|
36
|
+
// remember current data
|
|
37
|
+
new Uint8Array(sim_buffer, 0, field_byte_size).set(new Uint8Array(field.buffer, 0, field_byte_size))
|
|
38
|
+
|
|
39
|
+
let ping_pong_index = 0;
|
|
40
|
+
|
|
41
|
+
const DIFFUSE_STEPS = 2;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < 3; i++) {
|
|
44
|
+
// perform diffusion
|
|
45
|
+
let source = new Float32Array(sim_buffer, attribute_byte_size * i, attribute_cell_count)
|
|
46
|
+
let target = new Float32Array(sim_buffer, field_byte_size + attribute_byte_size * i, attribute_cell_count);
|
|
47
|
+
|
|
48
|
+
const ping_pong = [source, target];
|
|
49
|
+
|
|
50
|
+
ping_pong_index = 0;
|
|
51
|
+
|
|
52
|
+
for (let j = 0; j < DIFFUSE_STEPS; j++) {
|
|
53
|
+
|
|
54
|
+
source = ping_pong[ping_pong_index % 2];
|
|
55
|
+
target = ping_pong[(ping_pong_index + 1) % 2];
|
|
56
|
+
|
|
57
|
+
v3_grid_apply_diffusion(target, source, resolution);
|
|
58
|
+
|
|
59
|
+
ping_pong_index++;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ping_pong_index = DIFFUSE_STEPS;
|
|
65
|
+
|
|
66
|
+
const inputs = [];
|
|
67
|
+
const outputs = [];
|
|
68
|
+
|
|
69
|
+
for (let i = 0; i < 3; i++) {
|
|
70
|
+
const source = ping_pong_index % 2
|
|
71
|
+
const target = (ping_pong_index + 1) % 2
|
|
72
|
+
|
|
73
|
+
inputs[i] = new Float32Array(
|
|
74
|
+
sim_buffer, source * field_byte_size + attribute_byte_size * i, attribute_cell_count
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
outputs[i] = new Float32Array(
|
|
78
|
+
sim_buffer, target * field_byte_size + attribute_byte_size * i, attribute_cell_count
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
ping_pong_index++;
|
|
83
|
+
|
|
84
|
+
// advection
|
|
85
|
+
v3_grid_apply_advection_forward(outputs, inputs, resolution, time_delta_in_seconds);
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if (ping_pong_index % 2 !== 0) {
|
|
89
|
+
// data is not at source
|
|
90
|
+
field.attachBuffer(sim_buffer, field_byte_size);
|
|
91
|
+
} else {
|
|
92
|
+
field.attachBuffer(sim_buffer, 0);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import EmptyView from "../../../view/elements/EmptyView.js";
|
|
2
|
+
import { float2uint8 } from "../../../core/binary/float2uint8.js";
|
|
3
|
+
import { max2 } from "../../../core/math/max2.js";
|
|
4
|
+
import { Sampler2D } from "../../graphics/texture/sampler/Sampler2D.js";
|
|
5
|
+
import { CanvasView } from "../../../view/elements/CanvasView.js";
|
|
6
|
+
|
|
7
|
+
export class SliceVisualiser extends EmptyView {
|
|
8
|
+
|
|
9
|
+
#data = null;
|
|
10
|
+
#views = [];
|
|
11
|
+
#slices = [];
|
|
12
|
+
#resolution = [0, 0, 0];
|
|
13
|
+
#imageData = null;
|
|
14
|
+
#channelCount = 1
|
|
15
|
+
#drawMethod = (out, out_offset, sample, channelCount) => {
|
|
16
|
+
let i = 0;
|
|
17
|
+
|
|
18
|
+
for (; i < channelCount; i++) {
|
|
19
|
+
out[out_offset + i] = float2uint8(sample[i])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (channelCount < 4) {
|
|
23
|
+
out[out_offset + 3] = 255;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
*/
|
|
32
|
+
constructor() {
|
|
33
|
+
super({ classList: ['slice-visualizer'] });
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
this.on.linked.add(this.layout, this);
|
|
37
|
+
this.size.onChanged.add(this.layout, this);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
draw() {
|
|
41
|
+
|
|
42
|
+
const resolution_x = this.#resolution[0];
|
|
43
|
+
const resolution_y = this.#resolution[1];
|
|
44
|
+
const resolution_z = this.#resolution[2];
|
|
45
|
+
|
|
46
|
+
const imageData = this.#imageData;
|
|
47
|
+
|
|
48
|
+
const slice_size = resolution_x * resolution_y;
|
|
49
|
+
|
|
50
|
+
const data = this.#data;
|
|
51
|
+
|
|
52
|
+
const sample = new Float32Array(this.#channelCount);
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < resolution_z; i++) {
|
|
55
|
+
const view = this.#views[i];
|
|
56
|
+
|
|
57
|
+
for (let j = 0; j < slice_size; j++) {
|
|
58
|
+
const j4 = j * 4;
|
|
59
|
+
|
|
60
|
+
for (let k = 0; k < this.#channelCount; k++) {
|
|
61
|
+
const channel = data[slice_size * k + j];
|
|
62
|
+
|
|
63
|
+
sample[k] = channel;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.#drawMethod(imageData.data, j4, sample, this.#channelCount);
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
view.context2d.putImageData(imageData, 0, 0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
layout() {
|
|
75
|
+
|
|
76
|
+
const resolution_x = this.#resolution[0];
|
|
77
|
+
const resolution_y = this.#resolution[1];
|
|
78
|
+
const resolution_z = this.#resolution[2];
|
|
79
|
+
|
|
80
|
+
const width = this.size.x;
|
|
81
|
+
|
|
82
|
+
const grid_width = max2(1, Math.floor(width / resolution_x));
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < resolution_z; i++) {
|
|
85
|
+
const view = this.#views[i];
|
|
86
|
+
|
|
87
|
+
const grid_x = i % grid_width;
|
|
88
|
+
const grid_y = Math.floor(i / grid_width);
|
|
89
|
+
|
|
90
|
+
view.position.set(grid_x * resolution_x, grid_y * resolution_y);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* @param {ArrayBuffer} data
|
|
97
|
+
* @param {number[]} resolution
|
|
98
|
+
*/
|
|
99
|
+
setData(
|
|
100
|
+
data,
|
|
101
|
+
resolution) {
|
|
102
|
+
this.#data = new Float32Array(data);
|
|
103
|
+
this.#resolution = resolution;
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
const resolution_x = this.#resolution[0];
|
|
107
|
+
const resolution_y = this.#resolution[1];
|
|
108
|
+
const resolution_z = this.#resolution[2];
|
|
109
|
+
|
|
110
|
+
const slice_size = resolution_x * resolution_y;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @type {ImageData|null}
|
|
115
|
+
*/
|
|
116
|
+
this.#imageData = null;
|
|
117
|
+
|
|
118
|
+
let slice_index = 0;
|
|
119
|
+
|
|
120
|
+
for (; slice_index < resolution_z; slice_index++) {
|
|
121
|
+
this.#slices[slice_index] = new Sampler2D(new Float32Array(data, slice_index * slice_size * 4, slice_size), 1, resolution_x, resolution_y);
|
|
122
|
+
|
|
123
|
+
let child;
|
|
124
|
+
if (slice_index + 1 >= this.#views.length) {
|
|
125
|
+
child = new CanvasView();
|
|
126
|
+
this.#views[slice_index] = child
|
|
127
|
+
this.addChild(child);
|
|
128
|
+
} else {
|
|
129
|
+
child = this.#views[slice_index];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
child.size.set(resolution_x, resolution_y);
|
|
133
|
+
|
|
134
|
+
if (this.#imageData === null) {
|
|
135
|
+
this.#imageData = child.context2d.getImageData(0, 0, resolution_x, resolution_y);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
child.css({
|
|
139
|
+
position: "absolute",
|
|
140
|
+
left: 0,
|
|
141
|
+
top: 0
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
this.#views[slice_index] = child;
|
|
145
|
+
|
|
146
|
+
this.addChild(child);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//remove left-overs
|
|
150
|
+
|
|
151
|
+
for (; slice_index < this.#slices.length; slice_index++) {
|
|
152
|
+
|
|
153
|
+
this.#slices.splice(slice_index, 1);
|
|
154
|
+
const view = this.#views.splice(slice_index, 1)[0];
|
|
155
|
+
|
|
156
|
+
this.removeChild(view);
|
|
157
|
+
|
|
158
|
+
slice_index--;
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.layout();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
}
|
|
@@ -1,116 +1,18 @@
|
|
|
1
1
|
import { EngineHarness } from "../../EngineHarness.js";
|
|
2
|
-
import { SingleChannelSampler3D } from "../../graphics/texture/3d/SingleChannelSampler3D.js";
|
|
3
2
|
import GUIElementSystem from "../../ecs/gui/GUIElementSystem.js";
|
|
4
3
|
import EntityBuilder from "../../ecs/EntityBuilder.js";
|
|
5
4
|
import GUIElement from "../../ecs/gui/GUIElement.js";
|
|
6
|
-
import EmptyView from "../../../view/elements/EmptyView.js";
|
|
7
|
-
import { Sampler2D } from "../../graphics/texture/sampler/Sampler2D.js";
|
|
8
|
-
import { CanvasView } from "../../../view/elements/CanvasView.js";
|
|
9
|
-
import { float2uint8 } from "../../../core/binary/float2uint8.js";
|
|
10
|
-
import { clamp } from "../../../core/math/clamp.js";
|
|
11
|
-
import { randomIntegerBetween } from "../../../core/math/random/randomIntegerBetween.js";
|
|
12
|
-
import { seededRandom } from "../../../core/math/random/seededRandom.js";
|
|
13
5
|
import { BehaviorComponent } from "../../intelligence/behavior/ecs/BehaviorComponent.js";
|
|
14
6
|
import { BehaviorSystem } from "../../intelligence/behavior/ecs/BehaviorSystem.js";
|
|
15
|
-
import { v3_grid_apply_diffusion } from "./solver/v3_grid_apply_diffusion.js";
|
|
16
|
-
import { max2 } from "../../../core/math/max2.js";
|
|
17
7
|
import { MetricCollection } from "../../development/performance/MetricCollection.js";
|
|
18
8
|
import { PeriodicConsolePrinter } from "../../development/performance/monitor/PeriodicConsolePrinter.js";
|
|
19
9
|
import { MetricStatistics } from "../../development/performance/MetricStatistics.js";
|
|
10
|
+
import { FluidField } from "./FluidField.js";
|
|
11
|
+
import { FluidSimulator } from "./FluidSimulator.js";
|
|
12
|
+
import { SliceVisualiser } from "./SliceVisualiser.js";
|
|
20
13
|
|
|
21
14
|
const harness = new EngineHarness();
|
|
22
15
|
|
|
23
|
-
class SliceVisualiser extends EmptyView {
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
* @param {SingleChannelSampler3D} source
|
|
28
|
-
*/
|
|
29
|
-
constructor(source) {
|
|
30
|
-
super({ classList: ['slice-visualizer'] });
|
|
31
|
-
|
|
32
|
-
this.source = source;
|
|
33
|
-
|
|
34
|
-
const slices = [];
|
|
35
|
-
const views = [];
|
|
36
|
-
|
|
37
|
-
const resolution = source.resolution;
|
|
38
|
-
const resolution_x = resolution[0];
|
|
39
|
-
const resolution_y = resolution[1];
|
|
40
|
-
const resolution_z = resolution[2];
|
|
41
|
-
|
|
42
|
-
const slice_size = resolution_x * resolution_y;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
* @type {ImageData|null}
|
|
47
|
-
*/
|
|
48
|
-
let imageData = null;
|
|
49
|
-
|
|
50
|
-
for (let i = 0; i < resolution_z; i++) {
|
|
51
|
-
slices[i] = new Sampler2D(new Float32Array(source.data.buffer, i * slice_size * 4, slice_size), 1, resolution_x, resolution_y);
|
|
52
|
-
|
|
53
|
-
const child = new CanvasView();
|
|
54
|
-
child.size.set(resolution_x, resolution_y);
|
|
55
|
-
|
|
56
|
-
if (imageData === null) {
|
|
57
|
-
imageData = child.context2d.getImageData(0, 0, resolution_x, resolution_y);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
child.css({
|
|
61
|
-
position: "absolute",
|
|
62
|
-
left: 0,
|
|
63
|
-
top: 0
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
views[i] = child;
|
|
67
|
-
|
|
68
|
-
this.addChild(child);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.draw = () => {
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < resolution_z; i++) {
|
|
74
|
-
const view = views[i];
|
|
75
|
-
const slice = slices[i];
|
|
76
|
-
|
|
77
|
-
for (let j = 0; j < slice_size; j++) {
|
|
78
|
-
const j4 = j * 4;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const uint8_value = float2uint8(slice.data[j]);
|
|
82
|
-
|
|
83
|
-
imageData.data[j4] = clamp(uint8_value, 0, 255);
|
|
84
|
-
imageData.data[j4 + 3] = 255;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
view.context2d.putImageData(imageData, 0, 0);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const layout = () => {
|
|
93
|
-
const width = this.size.x;
|
|
94
|
-
|
|
95
|
-
const grid_width = max2(1, Math.floor(width / resolution_x));
|
|
96
|
-
|
|
97
|
-
for (let i = 0; i < resolution_z; i++) {
|
|
98
|
-
const view = views[i];
|
|
99
|
-
|
|
100
|
-
const grid_x = i % grid_width;
|
|
101
|
-
const grid_y = Math.floor(i / grid_width);
|
|
102
|
-
|
|
103
|
-
view.position.set(grid_x * resolution_x, grid_y * resolution_y);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
this.on.linked.add(layout);
|
|
108
|
-
this.size.onChanged.add(layout);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
16
|
const metrics = new MetricCollection();
|
|
115
17
|
|
|
116
18
|
metrics.create({ name: 'sim' })
|
|
@@ -123,56 +25,49 @@ function main(engine) {
|
|
|
123
25
|
|
|
124
26
|
const ecd = engine.entityManager.dataset;
|
|
125
27
|
|
|
126
|
-
const
|
|
28
|
+
const field = new FluidField();
|
|
127
29
|
|
|
128
|
-
|
|
30
|
+
field.addAttribute('x');
|
|
31
|
+
field.addAttribute('y');
|
|
32
|
+
field.addAttribute('z');
|
|
129
33
|
|
|
130
|
-
|
|
34
|
+
field.resolution = [32, 32, 64];
|
|
131
35
|
|
|
132
|
-
|
|
36
|
+
field.build();
|
|
133
37
|
|
|
134
|
-
|
|
38
|
+
field.setAttributeAt(0, 15, 16, 0, -100);
|
|
39
|
+
field.setAttributeAt(0, 17, 16, 0, 100);
|
|
40
|
+
field.setAttributeAt(1, 16, 15, 0, -100);
|
|
41
|
+
field.setAttributeAt(1, 16, 17, 0, 100);
|
|
135
42
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
random()
|
|
141
|
-
);
|
|
43
|
+
field.setAttributeAt(2, 15, 16, 0, 100);
|
|
44
|
+
field.setAttributeAt(2, 17, 16, 0, 100);
|
|
45
|
+
field.setAttributeAt(2, 16, 15, 0, 100);
|
|
46
|
+
field.setAttributeAt(2, 16, 17, 0, 100);
|
|
142
47
|
|
|
143
|
-
}
|
|
144
48
|
|
|
49
|
+
const sim = new FluidSimulator();
|
|
145
50
|
|
|
146
|
-
|
|
51
|
+
|
|
52
|
+
const slice_view = new SliceVisualiser();
|
|
147
53
|
|
|
148
54
|
slice_view.size.set(432, 1000);
|
|
149
55
|
slice_view.scale.setScalar(2);
|
|
150
56
|
slice_view.transformOrigin.set(0, 0);
|
|
151
57
|
|
|
152
|
-
|
|
153
|
-
const sampler_1 = sampler.clone();
|
|
154
|
-
|
|
155
|
-
const ping_pong = [sampler_0, sampler_1];
|
|
156
|
-
let ping_pong_index = 0;
|
|
58
|
+
slice_view.setData(field.buffer, field.resolution);
|
|
157
59
|
|
|
158
60
|
|
|
159
61
|
new EntityBuilder()
|
|
160
62
|
.add(BehaviorComponent.looping_function(time_delta => {
|
|
161
63
|
|
|
162
|
-
const source = ping_pong[ping_pong_index % 2];
|
|
163
|
-
const target = ping_pong[(ping_pong_index + 1) % 2];
|
|
164
|
-
|
|
165
|
-
ping_pong_index++;
|
|
166
|
-
|
|
167
64
|
const t0 = performance.now();
|
|
168
65
|
|
|
169
|
-
|
|
170
|
-
v3_grid_apply_diffusion(target.data, source.data, source.resolution);
|
|
171
|
-
}
|
|
66
|
+
sim.step(field, 0.015);
|
|
172
67
|
|
|
173
68
|
metrics.get('sim').record(performance.now() - t0);
|
|
174
69
|
|
|
175
|
-
|
|
70
|
+
slice_view.setData(field.buffer, field.resolution);
|
|
176
71
|
|
|
177
72
|
slice_view.draw();
|
|
178
73
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { scs3d_sample_linear } from "../../../graphics/texture/3d/scs3d_sample_linear.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {Float32Array[]} outputs
|
|
6
|
+
* @param {Float32Array[]} inputs
|
|
7
|
+
* @param {number[]} resolution
|
|
8
|
+
* @param {number} time_delta
|
|
9
|
+
*/
|
|
10
|
+
export function v3_grid_apply_advection_forward(outputs, inputs, resolution, time_delta) {
|
|
11
|
+
|
|
12
|
+
const res_x = resolution[0];
|
|
13
|
+
const res_y = resolution[1];
|
|
14
|
+
const res_z = resolution[2];
|
|
15
|
+
|
|
16
|
+
const input_x = inputs[0];
|
|
17
|
+
const input_y = inputs[1];
|
|
18
|
+
const input_z = inputs[2];
|
|
19
|
+
|
|
20
|
+
const slice_size = res_y * res_x;
|
|
21
|
+
|
|
22
|
+
for (
|
|
23
|
+
let z = 0;
|
|
24
|
+
z < res_z - 1;
|
|
25
|
+
z++
|
|
26
|
+
) {
|
|
27
|
+
|
|
28
|
+
const current_z_slice_offset = z * slice_size;
|
|
29
|
+
|
|
30
|
+
for (
|
|
31
|
+
let y = 0;
|
|
32
|
+
y < res_y;
|
|
33
|
+
y++
|
|
34
|
+
) {
|
|
35
|
+
const current_y_line_offset = y * res_x;
|
|
36
|
+
|
|
37
|
+
for (let x = 0; x < res_x; x++) {
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const current_index = current_z_slice_offset + current_y_line_offset + x;
|
|
41
|
+
|
|
42
|
+
const v_x = input_x[current_index]
|
|
43
|
+
const v_y = input_y[current_index]
|
|
44
|
+
const v_z = input_z[current_index]
|
|
45
|
+
|
|
46
|
+
// compute previous position of the particle
|
|
47
|
+
const s_x = x - v_x * time_delta;
|
|
48
|
+
const s_y = y - v_y * time_delta;
|
|
49
|
+
const s_z = z - v_z * time_delta;
|
|
50
|
+
|
|
51
|
+
// sample values at previous position
|
|
52
|
+
const new_x = scs3d_sample_linear(input_x, res_x, res_y, res_z, s_x, s_y, s_z);
|
|
53
|
+
const new_y = scs3d_sample_linear(input_y, res_x, res_y, res_z, s_x, s_y, s_z);
|
|
54
|
+
const new_z = scs3d_sample_linear(input_z, res_x, res_y, res_z, s_x, s_y, s_z);
|
|
55
|
+
|
|
56
|
+
outputs[0][current_index] = new_x;
|
|
57
|
+
outputs[1][current_index] = new_y;
|
|
58
|
+
outputs[2][current_index] = new_z;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -5,18 +5,32 @@
|
|
|
5
5
|
* @param {number[]} resolution
|
|
6
6
|
*/
|
|
7
7
|
export function v3_grid_apply_diffusion(output, input, resolution) {
|
|
8
|
+
let sum = 0;
|
|
9
|
+
|
|
8
10
|
const res_x = resolution[0];
|
|
9
11
|
const res_y = resolution[1];
|
|
10
12
|
const res_z = resolution[2];
|
|
11
13
|
|
|
12
|
-
const factor = 1 / 7;
|
|
13
|
-
|
|
14
|
-
let sum = 0;
|
|
15
14
|
|
|
16
15
|
const slice_size = res_y * res_x;
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// TODO consider taking inspiration from blur filters and performing convolution in 3 separate steps, one per axis. This would require fewer samples in total and potentially offer better cache utilization
|
|
18
|
+
|
|
19
|
+
const last_z_slice_offset = (res_z - 1) * slice_size;
|
|
20
|
+
const last_y_line_offset = (res_y - 1) * res_x;
|
|
21
|
+
|
|
22
|
+
for (
|
|
23
|
+
let current_z_slice_offset = 0;
|
|
24
|
+
current_z_slice_offset <= last_z_slice_offset;
|
|
25
|
+
current_z_slice_offset += slice_size
|
|
26
|
+
) {
|
|
27
|
+
|
|
28
|
+
for (
|
|
29
|
+
let current_y_line_offset = 0;
|
|
30
|
+
current_y_line_offset <= last_y_line_offset;
|
|
31
|
+
current_y_line_offset += res_x
|
|
32
|
+
) {
|
|
33
|
+
|
|
20
34
|
for (let x = 0; x < res_x; x++) {
|
|
21
35
|
|
|
22
36
|
let sample_count = 1;
|
|
@@ -24,39 +38,40 @@ export function v3_grid_apply_diffusion(output, input, resolution) {
|
|
|
24
38
|
sum = 0;
|
|
25
39
|
|
|
26
40
|
// sample cells around
|
|
27
|
-
if (
|
|
41
|
+
if (current_z_slice_offset > 0) {
|
|
28
42
|
sample_count++;
|
|
29
|
-
sum += input[
|
|
43
|
+
sum += input[current_z_slice_offset - slice_size + current_y_line_offset + x]
|
|
30
44
|
}
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
|
|
47
|
+
if (current_y_line_offset > 0) {
|
|
33
48
|
sample_count++;
|
|
34
|
-
sum += input[
|
|
49
|
+
sum += input[current_z_slice_offset + current_y_line_offset - res_x + x]
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
if (x > 0) {
|
|
38
53
|
sample_count++;
|
|
39
|
-
sum += input[
|
|
54
|
+
sum += input[current_z_slice_offset + current_y_line_offset + (x - 1)]
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
|
|
43
|
-
const current =
|
|
58
|
+
const current = current_z_slice_offset + current_y_line_offset + x;
|
|
44
59
|
|
|
45
60
|
sum += input[current];
|
|
46
61
|
|
|
47
62
|
if (x < res_x - 1) {
|
|
48
63
|
sample_count++;
|
|
49
|
-
sum += input[
|
|
64
|
+
sum += input[current_z_slice_offset + current_y_line_offset + (x + 1)]
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
if (
|
|
67
|
+
if (current_y_line_offset < last_y_line_offset) {
|
|
53
68
|
sample_count++;
|
|
54
|
-
sum += input[
|
|
69
|
+
sum += input[current_z_slice_offset + current_y_line_offset + res_x + x]
|
|
55
70
|
}
|
|
56
71
|
|
|
57
|
-
if (
|
|
72
|
+
if (current_z_slice_offset < last_z_slice_offset) {
|
|
58
73
|
sample_count++;
|
|
59
|
-
sum += input[
|
|
74
|
+
sum += input[current_z_slice_offset + slice_size + current_y_line_offset + x]
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
output[current] = sum / sample_count;
|