質問するログイン新規登録

回答編集履歴

1

調整

2026/09/25 09:27

投稿

yambejp
yambejp

スコア118675

answer CHANGED
@@ -1,4 +1,369 @@
1
1
  流体シミュレーションは普通はライブラリにまかせることが多いでしょう。
2
2
  自力でやるのであれば[ナビエ–ストークス方程式](https://ja.wikipedia.org/wiki/%E3%83%8A%E3%83%93%E3%82%A8%E2%80%93%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%82%B9%E6%96%B9%E7%A8%8B%E5%BC%8F)を利用することです。
3
3
  なお完全な物理シミュレーションは負荷が高いのでスパコンの世界になります。
4
- 個人が趣味でやるレベルであればある程度簡略化して疑似処理でやるしかないと思います。
4
+ 個人が趣味でやるレベルであればある程度簡略化して疑似処理でやるしかないと思います。
5
+
6
+ # 参考
7
+ 「ライブラリになしのjavascriptで流体シミュレーション書いてよチャッピー」とお願いしたらサンプルを書いてくれました。
8
+ マウスクリックで染料投入、マウスムーブで流体シミュレーションです。参考までに
9
+ ```HTML
10
+ <canvas id="canvas" width="600" height="600"></canvas>
11
+ <script>
12
+ const canvas = document.getElementById("canvas");
13
+ const ctx = canvas.getContext("2d");
14
+ const N = 100;
15
+ const SIZE = N * N;
16
+ const dt = 0.1;
17
+ const viscosity = 0.00001;
18
+ const pressureIterations = 30;
19
+ const u = new Float32Array(SIZE);
20
+ const v = new Float32Array(SIZE);
21
+ const uPrev = new Float32Array(SIZE);
22
+ const vPrev = new Float32Array(SIZE);
23
+ const pressure = new Float32Array(SIZE);
24
+ const pressurePrev = new Float32Array(SIZE);
25
+ const divergence = new Float32Array(SIZE);
26
+ const density = new Float32Array(SIZE);
27
+ const densityPrev = new Float32Array(SIZE);
28
+ function IX(x, y) {
29
+ return x + y * N;
30
+ }
31
+ function setBoundary(field, type) {
32
+ for (let i = 1; i < N - 1; i++) {
33
+ field[IX(i, 0)] =
34
+ type === 2
35
+ ? -field[IX(i, 1)]
36
+ : field[IX(i, 1)];
37
+ field[IX(i, N - 1)] =
38
+ type === 2
39
+ ? -field[IX(i, N - 2)]
40
+ : field[IX(i, N - 2)];
41
+ field[IX(0, i)] =
42
+ type === 1
43
+ ? -field[IX(1, i)]
44
+ : field[IX(1, i)];
45
+ field[IX(N - 1, i)] =
46
+ type === 1
47
+ ? -field[IX(N - 2, i)]
48
+ : field[IX(N - 2, i)];
49
+ }
50
+ field[IX(0, 0)] =
51
+ 0.5 * (
52
+ field[IX(1, 0)] +
53
+ field[IX(0, 1)]
54
+ );
55
+ field[IX(0, N - 1)] =
56
+ 0.5 * (
57
+ field[IX(1, N - 1)] +
58
+ field[IX(0, N - 2)]
59
+ );
60
+ field[IX(N - 1, 0)] =
61
+ 0.5 * (
62
+ field[IX(N - 2, 0)] +
63
+ field[IX(N - 1, 1)]
64
+ );
65
+ field[IX(N - 1, N - 1)] =
66
+ 0.5 * (
67
+ field[IX(N - 2, N - 1)] +
68
+ field[IX(N - 1, N - 2)]
69
+ );
70
+ }
71
+ function diffuse(field, source, diff, dt, boundaryType) {
72
+ const a = dt * diff * (N - 2) * (N - 2);
73
+ for (let k = 0; k < 20; k++) {
74
+ for (let y = 1; y < N - 1; y++) {
75
+ for (let x = 1; x < N - 1; x++) {
76
+ const i = IX(x, y);
77
+ field[i] =
78
+ (
79
+ source[i] +
80
+ a * (
81
+ field[IX(x - 1, y)] +
82
+ field[IX(x + 1, y)] +
83
+ field[IX(x, y - 1)] +
84
+ field[IX(x, y + 1)]
85
+ )
86
+ )
87
+ /
88
+ (1 + 4 * a);
89
+ }
90
+ }
91
+ setBoundary(field, boundaryType);
92
+ }
93
+ }
94
+ function advect(
95
+ field,
96
+ source,
97
+ uVelocity,
98
+ vVelocity,
99
+ dt,
100
+ boundaryType
101
+ ) {
102
+ const dt0 = dt * (N - 2);
103
+ for (let y = 1; y < N - 1; y++) {
104
+ for (let x = 1; x < N - 1; x++) {
105
+ const i = IX(x, y);
106
+ let px = x - dt0 * uVelocity[i];
107
+ let py = y - dt0 * vVelocity[i];
108
+ if (px < 0.5) px = 0.5;
109
+ if (px > N - 1.5) px = N - 1.5;
110
+ if (py < 0.5) py = 0.5;
111
+ if (py > N - 1.5) py = N - 1.5;
112
+ const x0 = Math.floor(px);
113
+ const x1 = x0 + 1;
114
+ const y0 = Math.floor(py);
115
+ const y1 = y0 + 1;
116
+ const sx = px - x0;
117
+ const sy = py - y0;
118
+ const i00 = IX(x0, y0);
119
+ const i10 = IX(x1, y0);
120
+ const i01 = IX(x0, y1);
121
+ const i11 = IX(x1, y1);
122
+ field[i] =
123
+ (1 - sy) *
124
+ (
125
+ (1 - sx) * source[i00] +
126
+ sx * source[i10]
127
+ )
128
+ +
129
+ sy *
130
+ (
131
+ (1 - sx) * source[i01] +
132
+ sx * source[i11]
133
+ );
134
+ }
135
+ }
136
+ setBoundary(field, boundaryType);
137
+ }
138
+ function project(u, v, pressure, divergence) {
139
+ for (let y = 1; y < N - 1; y++) {
140
+ for (let x = 1; x < N - 1; x++) {
141
+ const i = IX(x, y);
142
+ divergence[i] =
143
+ -0.5 * (
144
+ u[IX(x + 1, y)] -
145
+ u[IX(x - 1, y)] +
146
+ v[IX(x, y + 1)] -
147
+ v[IX(x, y - 1)]
148
+ ) / N;
149
+ pressure[i] = 0;
150
+ }
151
+ }
152
+ setBoundary(divergence, 0);
153
+ setBoundary(pressure, 0);
154
+ for (let k = 0; k < pressureIterations; k++) {
155
+ for (let y = 1; y < N - 1; y++) {
156
+ for (let x = 1; x < N - 1; x++) {
157
+ const i = IX(x, y);
158
+ pressure[i] =
159
+ (
160
+ divergence[i] +
161
+ pressure[IX(x - 1, y)] +
162
+ pressure[IX(x + 1, y)] +
163
+ pressure[IX(x, y - 1)] +
164
+ pressure[IX(x, y + 1)]
165
+ ) / 4;
166
+ }
167
+ }
168
+ setBoundary(pressure, 0);
169
+ }
170
+ for (let y = 1; y < N - 1; y++) {
171
+ for (let x = 1; x < N - 1; x++) {
172
+ const i = IX(x, y);
173
+ u[i] -=
174
+ 0.5 *
175
+ N *
176
+ (
177
+ pressure[IX(x + 1, y)] -
178
+ pressure[IX(x - 1, y)]
179
+ );
180
+ v[i] -=
181
+ 0.5 *
182
+ N *
183
+ (
184
+ pressure[IX(x, y + 1)] -
185
+ pressure[IX(x, y - 1)]
186
+ );
187
+ }
188
+ }
189
+ setBoundary(u, 1);
190
+ setBoundary(v, 2);
191
+ }
192
+ function velocityStep() {
193
+ uPrev.set(u);
194
+ vPrev.set(v);
195
+ diffuse(
196
+ u,
197
+ uPrev,
198
+ viscosity,
199
+ dt,
200
+ 1
201
+ );
202
+ diffuse(
203
+ v,
204
+ vPrev,
205
+ viscosity,
206
+ dt,
207
+ 2
208
+ );
209
+ project(
210
+ u,
211
+ v,
212
+ pressure,
213
+ divergence
214
+ );
215
+ uPrev.set(u);
216
+ vPrev.set(v);
217
+ advect(
218
+ u,
219
+ uPrev,
220
+ uPrev,
221
+ vPrev,
222
+ dt,
223
+ 1
224
+ );
225
+ advect(
226
+ v,
227
+ vPrev,
228
+ uPrev,
229
+ vPrev,
230
+ dt,
231
+ 2
232
+ );
233
+ project(
234
+ u,
235
+ v,
236
+ pressure,
237
+ divergence
238
+ );
239
+ }
240
+ function densityStep() {
241
+ densityPrev.set(density);
242
+ diffuse(
243
+ density,
244
+ densityPrev,
245
+ 0.00001,
246
+ dt,
247
+ 0
248
+ );
249
+ densityPrev.set(density);
250
+ advect(
251
+ density,
252
+ densityPrev,
253
+ u,
254
+ v,
255
+ dt,
256
+ 0
257
+ );
258
+ }
259
+ let mouseX = 50;
260
+ let mouseY = 50;
261
+ let previousMouseX = 50;
262
+ let previousMouseY = 50;
263
+ let mouseDown = false;
264
+ canvas.addEventListener("mousemove", function(e) {
265
+ const rect = canvas.getBoundingClientRect();
266
+ mouseX =
267
+ (e.clientX - rect.left)
268
+ / rect.width
269
+ * N;
270
+ mouseY =
271
+ (e.clientY - rect.top)
272
+ / rect.height
273
+ * N;
274
+ });
275
+ canvas.addEventListener("mousedown", function() {
276
+ mouseDown = true;
277
+ });
278
+ canvas.addEventListener("mouseup", function() {
279
+ mouseDown = false;
280
+ });
281
+ canvas.addEventListener("mouseleave", function() {
282
+ mouseDown = false;
283
+ });
284
+ function addMouseForce() {
285
+ const x = Math.floor(mouseX);
286
+ const y = Math.floor(mouseY);
287
+ if (
288
+ x < 2 ||
289
+ x >= N - 2 ||
290
+ y < 2 ||
291
+ y >= N - 2
292
+ ) {
293
+ return;
294
+ }
295
+ const dx = mouseX - previousMouseX;
296
+ const dy = mouseY - previousMouseY;
297
+ const radius = 4;
298
+ for (let yy = -radius; yy <= radius; yy++) {
299
+ for (let xx = -radius; xx <= radius; xx++) {
300
+ const px = x + xx;
301
+ const py = y + yy;
302
+ if (
303
+ px < 1 ||
304
+ px >= N - 1 ||
305
+ py < 1 ||
306
+ py >= N - 1
307
+ ) {
308
+ continue;
309
+ }
310
+ const distance =
311
+ Math.sqrt(xx * xx + yy * yy);
312
+ if (distance > radius) {
313
+ continue;
314
+ }
315
+ const strength =
316
+ 1 - distance / radius;
317
+ const i = IX(px, py);
318
+ u[i] += dx * 5 * strength;
319
+ v[i] += dy * 5 * strength;
320
+ if (mouseDown) {
321
+ density[i] += 5 * strength;
322
+ }
323
+ }
324
+ }
325
+ previousMouseX = mouseX;
326
+ previousMouseY = mouseY;
327
+ }
328
+ const image =
329
+ ctx.createImageData(
330
+ canvas.width,
331
+ canvas.height
332
+ );
333
+ function render() {
334
+ const pixels = image.data;
335
+ for (let py = 0; py < canvas.height; py++) {
336
+ for (let px = 0; px < canvas.width; px++) {
337
+ const x =
338
+ Math.floor(
339
+ px / canvas.width * N
340
+ );
341
+ const y =
342
+ Math.floor(
343
+ py / canvas.height * N
344
+ );
345
+ const d =
346
+ Math.min(
347
+ 255,
348
+ density[IX(x, y)] * 255
349
+ );
350
+ const index =
351
+ (px + py * canvas.width) * 4;
352
+ pixels[index] = d;
353
+ pixels[index + 1] = d * 0.15;
354
+ pixels[index + 2] = d * 0.15;
355
+ pixels[index + 3] = 255;
356
+ }
357
+ }
358
+ ctx.putImageData(image, 0, 0);
359
+ }
360
+ function update() {
361
+ addMouseForce();
362
+ velocityStep();
363
+ densityStep();
364
+ render();
365
+ requestAnimationFrame(update);
366
+ }
367
+ update();
368
+ </script>
369
+ ```