-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
776 lines (726 loc) · 54.5 KB
/
Copy pathindex.html
File metadata and controls
776 lines (726 loc) · 54.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
<!DOCTYPE html>
<html lang="ja">
<head>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-8270840061039680" crossorigin="anonymous"></script>
<meta charset="UTF-8">
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-RSYX6L0L61"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','G-RSYX6L0L61');</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ボイドシミュレーター — 無料オンライン計算機 | NovaSolver</title>
<meta name="description" content="ボイドシミュレーターで群れの創発的挙動を可視化。分離・整列・結合ルールで魚群や鳥群の動きをシミュレーション。群知能アルゴリズム、CAEシミュレーション。">
<meta property="og:title" content="ボイドシミュレーター — 群れの創発的挙動 | NovaSolver">
<meta property="og:description" content="ボイドシミュレーターで群れの創発的挙動を可視化。分離・整列・結合ルールで魚群や鳥群の動きをシミュレーション。群知能アルゴリズム、CAEシミュレーション。">
<meta property="og:locale" content="ja_JP">
<meta property="og:locale:alternate" content="en_US">
<meta property="og:locale:alternate" content="zh_CN">
<meta property="og:image" content="https://novasolver.jp/assets/images/general/blueprint.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Boids flocking simulation showing emergent collective behavior">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@novasolver_jp">
<meta name="twitter:image" content="https://novasolver.jp/assets/images/general/blueprint.jpg">
<meta name="robots" content="index,follow">
<link rel="canonical" href="https://novasolver.jp/tools/boids-flocking.html">
<link rel="alternate" hreflang="ja" href="https://novasolver.jp/tools/boids-flocking.html">
<link rel="alternate" hreflang="en" href="https://novasolver.jp/en/tools/boids-flocking.html">
<link rel="alternate" hreflang="zh-Hans" href="https://novasolver.jp/zh/tools/boids-flocking.html">
<link rel="alternate" hreflang="x-default" href="https://novasolver.jp/tools/boids-flocking.html">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="256x256" href="/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous">
<script>window.MathJax={tex:{inlineMath:[['$','$']],displayMath:[['$$','$$'],['\\[','\\]']]},options:{skipHtmlTags:['script','noscript','style','textarea','pre']}};</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" async></script>
<script type="application/ld+json">{
"@context": "https://schema.org",
"@graph": [
{
"@type": "SoftwareApplication",
"name": "ボイドシミュレーター",
"description": "ボイド(Boids)モデルで魚群・鳥群の創発的な集団行動をシミュレート。分離・整列・結合という3つのルールの重みをスライダーで操作し、捕食者や引力源の追加で群知能の挙動を可視化できます。",
"url": "https://novasolver.jp/tools/boids-flocking.html",
"applicationCategory": "EngineeringApplication",
"applicationSubCategory": "Physics Simulator",
"operatingSystem": "Web Browser",
"browserRequirements": "Requires JavaScript",
"isAccessibleForFree": true,
"offers": {"@type": "Offer", "price": "0", "priceCurrency": "JPY"},
"publisher":{"@type": "Organization", "name": "NovaSolver Project", "url": "https://novasolver.jp"},
"featureList": ["分離・整列・結合の3ルールによる群れシミュレーション","天敵(赤)と引力源(緑)の配置","プリセット(魚の群れ・鳥の群れ・混沌・密集編隊)"],
"keywords": "ボイド,群れ,創発的行動,マルチエージェント,粒子法,群知能,CAEシミュレーター",
"datePublished": "2026-04-06",
"dateModified": "2026-04-06",
"inLanguage": "ja"
},{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "ボイドアルゴリズムとは何ですか?",
"acceptedAnswer": {"@type": "Answer", "text": "1986年にCraig Reynoldsが提案した人工生命モデルです。「分離(近すぎる仲間から離れる)」「整列(周囲の平均速度に合わせる)」「結合(群れの中心に向かう)」の3ルールだけで、魚の群れや鳥の群れのような複雑な集団行動が自然に生まれます。"}
},{
"@type": "Question",
"name": "各スライダーはどのような影響を与えますか?",
"acceptedAnswer": {"@type": "Answer", "text": "分離の重みを上げると個体同士の衝突を避ける力が強くなります。整列の重みを上げると速度が揃い行進のような動きになります。結合の重みを上げると塊が小さく密集します。スライダーをリアルタイムに動かして変化を確認してください。"}
},{
"@type": "Question",
"name": "CAE(計算機援用工学)とどのような関係がありますか?",
"acceptedAnswer": {"@type": "Answer", "text": "ボイドの各個体は粒子法(SPH・DEM)の粒子に相当し、局所的な相互作用から大域的な挙動が生まれる点が共通します。また群知能アルゴリズムとして、構造最適化や群ロボット制御にも応用されています。"}
},{
"@type": "Question",
"name": "天敵(赤丸)や引力源(緑丸)はどうやって追加しますか?",
"acceptedAnswer": {"@type": "Answer", "text": "「天敵を追加」ボタンを押すか、キャンバスをクリックすると天敵(赤)が配置され、ボイドが逃げます。「引力源を追加」ボタンまたは右クリックで引力源(緑)を配置でき、ボイドが集まります。「クリア」ボタンで全てのオブジェクトを削除できます。"}
}
]
},{
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "NovaSolver", "item": "https://novasolver.jp"},{"@type": "ListItem", "position": 2, "name": "シミュレーター", "item": "https://novasolver.jp/tools/"},{"@type": "ListItem", "position": 3, "name": "ボイドシミュレーター", "item": "https://novasolver.jp/tools/boids-flocking.html"}
]
}
]
}</script>
<link rel="stylesheet" href="/tools/tools-common.css?v20260510r6">
<style>
*{ box-sizing: border-box; margin: 0; padding: 0; }:root{
--navy: #001F3F; --accent: #007BFF; --accent2: #00B4D8;
--bg: #F8F9FA; --card: #ffffff; --text: #1a1a2e;
--muted: #6c757d; --border: #dee2e6; --green: #00b894; --orange: #e17055;
}
html,body{overflow-x:hidden}
body{ font-family: 'Noto Sans JP', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
header { background: var(--navy); color: #fff; padding: 0 24px; }
.header-inner { max-width: 1200px; margin: 0 auto; display: flex; align-items: center; gap: 16px; height: 56px; }
.header-logo { font-size: 0.85rem; font-weight: 600; color: #fff; text-decoration: none; opacity: .8; }
.header-logo:hover { opacity: 1; }
.header-sep { color: rgba(255,255,255,.3); }
.header-title { font-size: 1rem; font-weight: 700; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; ;flex:1}
.hero { background: linear-gradient(135deg, var(--navy) 0%, #003875 60%, #0056a8 100%); color: #fff; padding: 36px 0 32px; }
.hero-inner { max-width: 1200px; margin: 0 auto; padding: 0 24px; }
.hero-badge { display: inline-block; background: rgba(0,180,216,.25); color: var(--accent2); font-size: 0.72rem; font-weight: 700; letter-spacing: .08em; padding: 4px 10px; border-radius: 20px; border: 1px solid rgba(0,180,216,.4); margin-bottom: 10px; text-transform: uppercase; }
.hero h1 { font-size: clamp(1.4rem, 3vw, 2rem); font-weight: 800; margin-bottom: 8px; }
.hero-sub { color: rgba(255,255,255,.75); font-size: 0.9rem; line-height: 1.6; max-width: 640px; }@media(max-width:820px){ .main { grid-template-columns: 1fr; } }.panel{ background: var(--card); border-radius: 14px; padding: 22px; box-shadow: 0 2px 16px rgba(0,0,0,.06); border: 1px solid var(--border); }.panel-title{ font-size: 0.7rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }.panel-title::after{ content: ''; flex: 1; height: 1px; background: var(--border); }.param-group{ margin-bottom: 14px; }.param-label{ display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; }.param-name{ font-size: 0.83rem; font-weight: 600; color: var(--text); }.param-value{ font-family: 'Roboto Mono', monospace; font-size: 0.82rem; color: var(--accent); font-weight: 600; background: rgba(0,123,255,.08); padding: 2px 8px; border-radius: 6px; min-width: 52px; text-align: right; }input[type=range]{ width: 100%; -webkit-appearance: none; height: 4px; border-radius: 2px; background: linear-gradient(to right, var(--accent) 0%, var(--accent) var(--pct,50%), var(--border) var(--pct,50%), var(--border) 100%); outline: none; cursor: pointer; }input[type=range]::-webkit-slider-thumb{ -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: var(--accent); cursor: pointer; box-shadow: 0 2px 6px rgba(0,123,255,.4); transition: transform .15s; }input[type=range]::-webkit-slider-thumb:hover{ transform: scale(1.2); }input[type=range]::-moz-range-thumb{ width: 16px; height: 16px; border-radius: 50%; background: var(--accent); cursor: pointer; border: none; }.divider{ height: 1px; background: var(--border); margin: 14px 0; }.btn{ display: inline-flex; align-items: center; gap: 6px; background: var(--accent); color: #fff; border: none; padding: 9px 18px; border-radius: 8px; cursor: pointer; font-size: 0.85rem; font-family: inherit; font-weight: 600; transition: background .2s; }.btn:hover{ background: #0056b3; }.btn.secondary{ background: var(--bg); color: var(--text); border: 1px solid var(--border); }.btn.secondary:hover{ background: var(--border); }.btn.danger{ background: #e74c3c; }.btn.danger:hover{ background: #c0392b; }.btn.green{ background: var(--green); }.btn.green:hover{ background: #00a884; }.btn-group{ display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }.preset-btn{ padding: 5px 10px; font-size: 0.76rem; border-radius: 6px; border: 1px solid var(--border); background: var(--bg); cursor: pointer; font-family: inherit; transition: all .2s; }.preset-btn:hover, .preset-btn.active{ border-color: var(--accent); color: var(--accent); background: rgba(0,123,255,.07); }.toggle-btn{ display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 7px; border: 1px solid var(--border); background: var(--bg); color: var(--text); font-size: 0.8rem; font-family: inherit; cursor: pointer; transition: all .2s; }.toggle-btn.active{ background: rgba(0,123,255,.1); border-color: var(--accent); color: var(--accent); font-weight: 600; }.stat-cards{ display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 14px; }.stat-card{ background: var(--card); border-radius: 10px; padding: 10px; text-align: center; border: 1px solid var(--border); }.stat-card .sv{ font-size: 1.1rem; font-weight: 700; color: var(--accent); font-family: 'Roboto Mono', monospace; }.stat-card .sl{ font-size: 0.68rem; color: var(--muted); margin-top: 2px; }.viz-panel{ background: var(--card); border-radius: 14px; padding: 22px; box-shadow: 0 2px 16px rgba(0,0,0,.06); border: 1px solid var(--border); }
#boidsCanvas { border-radius: 8px; background: #020d1a; display: block; width: 100%; cursor: crosshair; }.theory-box{ background: rgba(0,123,255,.04); border-left: 3px solid var(--accent); padding: 14px 16px; border-radius: 0 8px 8px 0; font-size: 0.85rem; line-height: 1.7; margin-top: 16px; }.theory-box h3{ font-size: 0.88rem; color: var(--accent); margin-bottom: 8px; font-weight: 700; }.cae-note{ background: rgba(0,180,216,.06); border: 1px solid rgba(0,180,216,.2); border-radius: 8px; padding: 12px 14px; font-size: 0.8rem; color: var(--muted); margin-top: 12px; line-height: 1.6; }.cae-note strong{ color: var(--accent2); }
.canvas-hint { font-size: 0.75rem; color: var(--muted); margin-top: 6px; text-align: center; }@media(max-width:860px){
.hero h1{font-size:1.15rem;margin-bottom:4px}
.hero-sub{font-size:.8rem;line-height:1.4}.panel,.viz-panel{padding:14px;border-radius:10px}.stat-cards{grid-template-columns:repeat(3,1fr)}
}@media(max-width:640px){
.header-sep{display:none}
.param-group{margin-bottom:2px}.field-header{margin-bottom:0}.param-label{margin-bottom:0}.num-wrap{gap:2px}.num-input{width:56px;padding:1px 3px;font-size:.74rem;border-radius:4px}.num-unit{font-size:.68rem;min-width:16px}.param-name{font-size:.71rem}.param-value{font-size:.71rem;padding:1px 5px;min-width:auto}input[type=range]{height:3px;margin:0}select{padding:3px 5px;font-size:.74rem;margin-bottom:2px}.divider{margin:3px 0;opacity:.4}.panel{padding:7px}.panel-title{margin-bottom:4px;font-size:.58rem}
}.main>*{min-width:0;max-width:100%}.panel{overflow-x:hidden}.theory-box,.cae-note{overflow-x:auto;max-width:100%}/* === Mobile Compact === */
@media(max-width:820px){
.hero h1{font-size:1.15rem;margin-bottom:4px}
.hero-sub{font-size:0.78rem;line-height:1.4}
.hero-badge{margin-bottom:6px;font-size:0.65rem;padding:3px 8px}.viz-panel{order:-1;}.panel{order:1;padding:14px;max-height:none;overflow-y:visible;}.panel-title{margin-bottom:10px;font-size:0.65rem}.param-group{margin-bottom:8px}.param-label{margin-bottom:3px}.param-name{font-size:0.78rem}.param-value{font-size:0.76rem;padding:1px 6px}input[type=range]{height:3px;margin:2px 0}.divider{margin:8px 0}.theory-box{font-size:0.78rem;padding:10px 12px;margin-top:10px}.theory-box h3{font-size:0.8rem;margin-bottom:4px}.stat-cards{gap:6px}.stat-card{padding:8px}.stat-card .sv{font-size:0.95rem}.stat-card .sl{font-size:0.62rem}.btn{padding:7px 14px;font-size:0.8rem}.btn-group{gap:6px;margin-bottom:8px}.preset-btn{padding:4px 8px;font-size:0.7rem}.cae-note{padding:8px 10px;font-size:0.73rem;margin-top:8px}.toggle-btn{padding:4px 10px;font-size:0.73rem}
}@media(max-width:640px){
.hero h1{font-size:1.05rem}
.hero-sub{display:none}
.hero-badge{margin-bottom:4px;font-size:0.6rem;padding:2px 6px}.panel-title{margin-bottom:8px}.param-group{margin-bottom:6px}.param-name{font-size:0.75rem}.param-value{font-size:0.73rem}.divider{margin:6px 0}.theory-box{padding:8px 10px;margin-top:8px}.viz-panel{padding:12px;border-radius:10px}.stat-card{padding:6px}.stat-card .sv{font-size:0.85rem}.btn{padding:6px 12px;font-size:0.78rem}.preset-btn{padding:3px 6px;font-size:0.68rem}
}.explanation{max-width:1200px;margin:0 auto;padding:40px 24px 60px}.explanation h2{font-size:19px;font-weight:700;color:var(--accent);margin:24px 0 10px;border-left:3px solid var(--accent);padding-left:12px}.explanation p{font-size:15px;line-height:1.8;color:var(--text);margin-bottom:12px}.fblock{background:rgba(0,123,255,.04);border:1px solid rgba(0,123,255,.15);border-radius:8px;padding:16px 20px;margin:14px 0;overflow-x:auto}.fblock p{margin-bottom:8px;font-size:14px;color:var(--text)}.chat{background:rgba(0,123,255,.04);border:1px solid rgba(0,123,255,.1);border-radius:12px;padding:16px;margin:20px 0}.cmsg{display:flex;gap:10px;margin-bottom:12px;align-items:flex-start}.cmsg .ico{font-size:22px;flex-shrink:0}.cmsg .bub{background:var(--card);border-radius:8px;padding:10px 14px;font-size:14px;line-height:1.7;color:var(--text);border:1px solid var(--border)}.cmsg.st .bub{border-left:3px solid var(--accent2)}.cmsg.pr .bub{border-left:3px solid #f59e0b}
.sliders-2col{display:grid;grid-template-columns:1fr;gap:0;}.sliders-2col .divider{grid-column:1/-1;margin:4px 0;}
/* Accordion (details/summary) for non-slider sections */
details.ctrl-section{margin-bottom:0;}
details.ctrl-section:not([open]) > :not(summary){display:none;}
details.ctrl-section>summary{list-style:none;cursor:pointer;user-select:none;-webkit-user-select:none;}
details.ctrl-section>summary::-webkit-details-marker{display:none;}
details.ctrl-section>summary .panel-title{margin-bottom:0;}
details.ctrl-section[open]>summary .panel-title{margin-bottom:10px;}
details.ctrl-section>summary .expand-icon{font-size:0.6rem;color:var(--muted);transition:transform .2s;float:right;}
details.ctrl-section[open]>summary .expand-icon{transform:rotate(180deg);}
/* Collapsible theory-box / cae-note */
details.theory-box>summary,details.cae-note>summary{list-style:none;cursor:pointer;user-select:none;-webkit-user-select:none;}
details.theory-box>summary::-webkit-details-marker,details.cae-note>summary::-webkit-details-marker{display:none;}
details.theory-box>summary h3{margin-bottom:0;}
details.theory-box[open]>summary h3{margin-bottom:6px;}
details.cae-note>summary{font-size:0.85rem;font-weight:600;}
details.theory-box>summary .expand-icon,details.cae-note>summary .expand-icon{font-size:0.6rem;color:var(--muted);transition:transform .2s;margin-left:6px;vertical-align:middle;}
details.theory-box[open]>summary .expand-icon,details.cae-note[open]>summary .expand-icon{transform:rotate(180deg);}
</style>
<meta property="og:type" content="article">
<meta property="og:url" content="https://novasolver.jp/tools/boids-flocking.html">
<script src="/tools/tools-common.js?v20260511r13"></script>
<link href="/tools/tool-ux-template.css?v20260529r1" rel="stylesheet"/>
</head>
<body>
<header>
<div class="header-inner">
<a href="/tools/index.html" class="header-logo">NovaSolver</a>
<span class="header-sep">›</span>
<span class="header-title">ボイドシミュレーター</span>
<nav class="lang-nav">
<a href="/tools/boids-flocking.html" class="active">JA</a>
<a href="/en/tools/boids-flocking.html">EN</a>
<a href="/zh/tools/boids-flocking.html">ZH</a>
</nav>
<a href="/tools/index.html" class="header-back"><i class="fas fa-arrow-left"></i> 戻る</a>
</div>
</header>
<div class="hero">
<div class="hero-inner">
<div class="hero-badge"><i class="fas fa-fish"></i> 対話型シミュレーター</div>
<h1>ボイドシミュレーター — 群れの創発的挙動</h1>
<p class="hero-sub">分離・整列・結合という単純な3ルールだけで、画面上に魚群や鳥群のようなうねりが立ち上がります。重みをスライダーで動かし、捕食者や引力源も置きながら、群知能の不思議さを自分の手で確かめてください。</p>
</div>
</div>
<div class="main">
<!-- Left Panel -->
<div class="panel">
<div class="panel-title"><i class="fas fa-sliders-h"></i> パラメータ設定</div>
<div class="sliders-2col">
<div class="param-group">
<div class="param-label">
<span class="param-name">ボイド数</span>
<div class="num-wrap"><input type="number" class="num-input" id="countVal" value="100" min="20" max="200" step="5" oninput="syncFromNum('boidCount','countVal')"><span class="num-unit"></span></div>
</div>
<input type="range" id="boidCount" min="20" max="200" step="5" value="100" oninput="syncToNum('boidCount','countVal');updateSlider(this,'countVal',0,'');onParamChange()">
</div>
<div class="param-group">
<div class="param-label">
<span class="param-name">最大速度</span>
<div class="num-wrap"><input type="number" class="num-input" id="speedVal" value="2.5" min="0.5" max="6" step="0.1" oninput="syncFromNum('maxSpeed','speedVal')"><span class="num-unit"></span></div>
</div>
<input type="range" id="maxSpeed" min="0.5" max="6" step="0.1" value="2.5" oninput="syncToNum('maxSpeed','speedVal');updateSlider(this,'speedVal',1,'');onParamChange()">
</div>
<div class="param-group">
<div class="param-label">
<span class="param-name">視野半径</span>
<div class="num-wrap"><input type="number" class="num-input" id="rangeVal" value="60" min="20" max="150" step="5" oninput="syncFromNum('visualRange','rangeVal')"><span class="num-unit"></span></div>
</div>
<input type="range" id="visualRange" min="20" max="150" step="5" value="60" oninput="syncToNum('visualRange','rangeVal');updateSlider(this,'rangeVal',0,'');onParamChange()">
</div>
<div class="divider"></div>
<div class="param-group">
<div class="param-label">
<span class="param-name">分離の重み</span>
<div class="num-wrap"><input type="number" class="num-input" id="sepVal" value="1.5" min="0" max="5" step="0.1" oninput="syncFromNum('sepWeight','sepVal')"><span class="num-unit"></span></div>
</div>
<input type="range" id="sepWeight" min="0" max="5" step="0.1" value="1.5" oninput="syncToNum('sepWeight','sepVal');updateSlider(this,'sepVal',1,'');onParamChange()">
</div>
<div class="param-group">
<div class="param-label">
<span class="param-name">整列の重み</span>
<div class="num-wrap"><input type="number" class="num-input" id="aliVal" value="1.0" min="0" max="5" step="0.1" oninput="syncFromNum('aliWeight','aliVal')"><span class="num-unit"></span></div>
</div>
<input type="range" id="aliWeight" min="0" max="5" step="0.1" value="1.0" oninput="syncToNum('aliWeight','aliVal');updateSlider(this,'aliVal',1,'');onParamChange()">
</div>
<div class="param-group">
<div class="param-label">
<span class="param-name">結合の重み</span>
<div class="num-wrap"><input type="number" class="num-input" id="cohVal" value="1.0" min="0" max="5" step="0.1" oninput="syncFromNum('cohWeight','cohVal')"><span class="num-unit"></span></div>
</div>
<input type="range" id="cohWeight" min="0" max="5" step="0.1" value="1.0" oninput="syncToNum('cohWeight','cohVal');updateSlider(this,'cohVal',1,'');onParamChange()">
</div>
</div><!-- /sliders-2col -->
<div class="divider"></div>
<details class="ctrl-section">
<summary><div class="panel-title"><i class="fas fa-magic"></i> プリセット <i class="fas fa-chevron-down expand-icon"></i></div></summary>
<div class="btn-group">
<button class="preset-btn" onclick="applyPreset('fish')">🐟 魚の群れ</button>
<button class="preset-btn" onclick="applyPreset('bird')">🐦 鳥の群れ</button>
<button class="preset-btn" onclick="applyPreset('chaos')">💥 混沌</button>
<button class="preset-btn" onclick="applyPreset('tight')">🎯 密集隊形</button>
</div>
</details>
<div class="divider"></div>
<details class="ctrl-section">
<summary><div class="panel-title"><i class="fas fa-eye"></i> 表示オプション <i class="fas fa-chevron-down expand-icon"></i></div></summary>
<div class="btn-group">
<button class="toggle-btn" id="showRangeBtn" onclick="toggleOpt('showRange',this)"><i class="fas fa-circle"></i> 視野圏</button>
<button class="toggle-btn" id="showVelBtn" onclick="toggleOpt('showVel',this)"><i class="fas fa-arrow-right"></i> 速度ベクトル</button>
<button class="toggle-btn active" id="showTrailBtn" onclick="toggleOpt('showTrail',this)"><i class="fas fa-wave-square"></i> 軌跡</button>
</div>
</details>
<div class="divider"></div>
<details class="ctrl-section">
<summary><div class="panel-title"><i class="fas fa-crosshairs"></i> インタラクション <i class="fas fa-chevron-down expand-icon"></i></div></summary>
<div class="btn-group">
<button class="btn danger" onclick="addPredator()" style="padding:7px 14px;font-size:.8rem;"><i class="fas fa-skull"></i> 天敵を追加</button>
<button class="btn green" onclick="addAttractor()" style="padding:7px 14px;font-size:.8rem;"><i class="fas fa-magnet"></i> 引力源を追加</button>
</div>
<div class="btn-group" style="margin-top:8px;">
<button class="btn secondary" onclick="resetBoids()" style="padding:7px 14px;font-size:.8rem;"><i class="fas fa-redo"></i> リセット</button>
</div>
<p style="font-size:0.72rem;color:var(--muted);margin-top:6px;">クリック→天敵 / 右クリック→引力源</p>
</details>
</div>
<!-- Right Viz Panel -->
<div class="viz-panel">
<div class="chart-card"><div class="chart-title"><i class="fas fa-calculator"></i> 計算結果</div>
<div class="stat-cards">
<div class="stat-card"><div class="sv" id="statCount">100</div><div class="sl">ボイド数</div></div>
<div class="stat-card"><div class="sv" id="statSpeed">—</div><div class="sl">平均速度</div></div>
<div class="stat-card"><div class="sv" id="statClusters">—</div><div class="sl">クラスター数</div></div>
</div>
</div>
<div class="chart-card"><div class="chart-title"><i class="fas fa-chart-line"></i> 群れ</div><canvas id="boidsCanvas"></canvas></div>
<p class="canvas-hint">クリック: 天敵追加 / 右クリック: 引力源追加 / タップ長押し: 引力源</p>
<div class="chart-card"><div class="chart-title"><i class="fas fa-square-root-alt"></i> 理論・主要公式</div>
<div class="theory-box">
<p>各ボイドは視野半径 $r$ 内の隣人に対して3つの力を計算する:</p>
<ul style="padding-left:1.2em;margin-top:8px;line-height:1.9;font-size:0.83rem;">
<li><strong>分離 (Separation):</strong> $\mathbf{F}_s = -\sum_{j \in N_{sep}}\frac{\mathbf{r}_{ij}}{|\mathbf{r}_{ij}|^2}$</li>
<li><strong>整列 (Alignment):</strong> $\mathbf{F}_a = \langle \mathbf{v}_j \rangle - \mathbf{v}_i$</li>
<li><strong>結合 (Cohesion):</strong> $\mathbf{F}_c = \langle \mathbf{x}_j \rangle - \mathbf{x}_i$</li>
</ul>
<p style="margin-top:8px;">最終加速度: $\mathbf{a}_i = w_s \mathbf{F}_s + w_a \mathbf{F}_a + w_c \mathbf{F}_c$</p>
<p style="margin-top:8px;font-size:0.8rem;color:var(--muted);">速度は $v_{max}$ でクランプ。端に達したら反対側から出現(トーラス空間)。</p>
</div>
</div>
</div>
</div>
<script>
// ========== Utility ==========
function onParamChange(){
params.n=parseInt(document.getElementById('boidCount').value);
params.maxSpeed=parseFloat(document.getElementById('maxSpeed').value);
params.visualRange=parseFloat(document.getElementById('visualRange').value);
params.sepWeight=parseFloat(document.getElementById('sepWeight').value);
params.aliWeight=parseFloat(document.getElementById('aliWeight').value);
params.cohWeight=parseFloat(document.getElementById('cohWeight').value);
if(boids.length!==params.n) resetBoids();
}function toggleOpt(key,btn){
opts[key]=!opts[key];
btn.classList.toggle('active',opts[key]);
}function applyPreset(name){
const presets={
fish:{n:120,maxSpeed:2.0,visualRange:55,sep:2.0,ali:1.2,coh:0.8},
bird:{n:80,maxSpeed:3.5,visualRange:80,sep:1.2,ali:1.5,coh:1.0},
chaos:{n:150,maxSpeed:5.0,visualRange:30,sep:0.3,ali:0.2,coh:0.1},
tight:{n:60,maxSpeed:1.5,visualRange:100,sep:0.8,ali:2.0,coh:2.5}
};
const p=presets[name];
if(!p) return;
document.getElementById('boidCount').value=p.n;
document.getElementById('maxSpeed').value=p.maxSpeed;
document.getElementById('visualRange').value=p.visualRange;
document.getElementById('sepWeight').value=p.sep;
document.getElementById('aliWeight').value=p.ali;
document.getElementById('cohWeight').value=p.coh;
updateSlider(document.getElementById('boidCount'),'countVal',0,'');
updateSlider(document.getElementById('maxSpeed'),'speedVal',1,'');
updateSlider(document.getElementById('visualRange'),'rangeVal',0,'');
updateSlider(document.getElementById('sepWeight'),'sepVal',1,'');
updateSlider(document.getElementById('aliWeight'),'aliVal',1,'');
updateSlider(document.getElementById('cohWeight'),'cohVal',1,'');
onParamChange();
}
// ========== Simulation ==========
const canvas=document.getElementById('boidsCanvas');
const ctx=canvas.getContext('2d');
let W=600,H=520;
const params={n:100,maxSpeed:2.5,visualRange:60,sepWeight:1.5,aliWeight:1.0,cohWeight:1.0};
const opts={showRange:false,showVel:false,showTrail:true};
let boids=[],predators=[],attractors=[];
function resizeCanvas(){
const container=canvas.parentElement;
W=Math.min(container.clientWidth-44, 900);
H=Math.max(Math.round(W*0.6),380);
var __novaW = W;
var __novaH = H;
var __novaDpr = Math.min(window.devicePixelRatio || 1, 2.5);
canvas.style.width = __novaW + 'px';
canvas.style.height = __novaH + 'px';
canvas.width = Math.round(__novaW * __novaDpr);
canvas.height = Math.round(__novaH * __novaDpr);
canvas.dataset.cssW = String(__novaW);
canvas.dataset.cssH = String(__novaH);
var __novaCtx = canvas.getContext && canvas.getContext('2d');
if (__novaCtx) __novaCtx.setTransform(__novaDpr, 0, 0, __novaDpr, 0, 0);
}function randBoid(){
const angle=Math.random()*Math.PI*2;
const spd=(Math.random()*0.5+0.5)*params.maxSpeed;
return {
x:Math.random()*W, y:Math.random()*H,
vx:Math.cos(angle)*spd, vy:Math.sin(angle)*spd,
trail:[]
};
}function resetBoids(){
boids=[];
for(let i=0;i<params.n;i++) boids.push(randBoid());
document.getElementById('statCount').textContent=params.n;
}function addPredator(){
predators.push({x:W/2+Math.random()*80-40, y:H/2+Math.random()*80-40, pulse:0});
}function addAttractor(){
attractors.push({x:W/2+Math.random()*80-40, y:H/2+Math.random()*80-40, pulse:0});
}function clearSpecial(){ predators=[]; attractors=[]; }// Simple O(n²) with early out
function stepBoids(){
const range=params.visualRange;
const sepRange=range*0.4;
const range2=range*range;
const sepRange2=sepRange*sepRange;
const maxForce=0.3;
const fleeRange=120,fleeRange2=fleeRange*fleeRange;
const attractRange=150,attractRange2=attractRange*attractRange;
for(let i=0;i<boids.length;i++){
const b=boids[i];
let sx=0,sy=0,ax=0,ay=0,cx=0,cy=0,nc=0;
for(let j=0;j<boids.length;j++){
if(i===j) continue;
const o=boids[j];
const dx=o.x-b.x, dy=o.y-b.y;
const d2=dx*dx+dy*dy;
if(d2>range2) continue;
// separation
if(d2<sepRange2 && d2>0){
const d=Math.sqrt(d2);
sx-=dx/d*(sepRange/d);
sy-=dy/d*(sepRange/d);
}
// alignment
ax+=o.vx; ay+=o.vy;
// cohesion
cx+=o.x; cy+=o.y;
nc++;
}let fx=0,fy=0;
if(nc>0){
// separation
fx+=sx*params.sepWeight;
fy+=sy*params.sepWeight;
// alignment
const avgVx=ax/nc, avgVy=ay/nc;
fx+=(avgVx-b.vx)*params.aliWeight*0.1;
fy+=(avgVy-b.vy)*params.aliWeight*0.1;
// cohesion
const comX=cx/nc, comY=cy/nc;
fx+=(comX-b.x)*params.cohWeight*0.002;
fy+=(comY-b.y)*params.cohWeight*0.002;
}// predator flee
for(const p of predators){
const dx=b.x-p.x, dy=b.y-p.y;
const d2=dx*dx+dy*dy;
if(d2<fleeRange2 && d2>0){
const d=Math.sqrt(d2);
fx+=dx/d*(fleeRange/d)*3;
fy+=dy/d*(fleeRange/d)*3;
}
}// attractor pull
for(const a of attractors){
const dx=a.x-b.x, dy=a.y-b.y;
const d2=dx*dx+dy*dy;
if(d2<attractRange2 && d2>0){
const d=Math.sqrt(d2);
fx+=dx/d*0.4;
fy+=dy/d*0.4;
}
}// clamp force
const fm=Math.sqrt(fx*fx+fy*fy);
if(fm>maxForce){ fx=fx/fm*maxForce; fy=fy/fm*maxForce; }b.vx+=fx; b.vy+=fy;
const spd=Math.sqrt(b.vx*b.vx+b.vy*b.vy);
if(spd>params.maxSpeed){ b.vx=b.vx/spd*params.maxSpeed; b.vy=b.vy/spd*params.maxSpeed; }if(spd<0.3){ b.vx*=1.05; b.vy*=1.05; }// trail
if(opts.showTrail){
b.trail.push({x:b.x,y:b.y});
if(b.trail.length>6) b.trail.shift();
}else{ b.trail=[]; }
b.x+=b.vx; b.y+=b.vy;
// wrap
if(b.x<0) b.x+=W; if(b.x>W) b.x-=W;
if(b.y<0) b.y+=H; if(b.y>H) b.y-=H;
}
}function drawBoid(b,i){
const angle=Math.atan2(b.vy,b.vx);
const size=6;
// trail
if(opts.showTrail && b.trail.length>1){
ctx.strokeStyle='rgba(0,180,216,0.18)';
ctx.lineWidth=1.5;
ctx.beginPath();
ctx.moveTo(b.trail[0].x,b.trail[0].y);
for(let t=1;t<b.trail.length;t++) ctx.lineTo(b.trail[t].x,b.trail[t].y);
ctx.stroke();
}// vision range
if(opts.showRange){
ctx.strokeStyle='rgba(0,123,255,0.08)';
ctx.lineWidth=1;
ctx.beginPath();
ctx.arc(b.x,b.y,params.visualRange,0,Math.PI*2);
ctx.stroke();
}// velocity vector
if(opts.showVel){
ctx.strokeStyle='rgba(0,180,216,0.5)';
ctx.lineWidth=1;
ctx.beginPath();
ctx.moveTo(b.x,b.y);
ctx.lineTo(b.x+b.vx*4,b.y+b.vy*4);
ctx.stroke();
}
// body
ctx.save();
ctx.translate(b.x,b.y);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(size,0);
ctx.lineTo(-size*0.6,size*0.45);
ctx.lineTo(-size*0.3,0);
ctx.lineTo(-size*0.6,-size*0.45);
ctx.closePath();
// color by speed
const spd=Math.sqrt(b.vx*b.vx+b.vy*b.vy);
const t=Math.min(spd/params.maxSpeed,1);
const r=Math.round(0+t*0); const g=Math.round(180+t*75); const bl=Math.round(216-t*60);
ctx.fillStyle=`rgb(${r},${g},${bl})`;
ctx.shadowColor='rgba(0,180,216,0.4)';
ctx.shadowBlur=4;
ctx.fill();
ctx.restore();
}function estimateClusters(){
if(boids.length===0) return 0;
const visited=new Uint8Array(boids.length);
let clusters=0;
const r2=params.visualRange*params.visualRange;
for(let i=0;i<boids.length;i++){
if(visited[i]) continue;
clusters++;
const stack=[i]; visited[i]=1;
while(stack.length>0){
const cur=stack.pop();
for(let j=0;j<boids.length;j++){
if(visited[j]) continue;
const dx=boids[j].x-boids[cur].x, dy=boids[j].y-boids[cur].y;
if(dx*dx+dy*dy<r2){ visited[j]=1; stack.push(j); }
}
}
}
return clusters;
}let frameCount=0;
function draw(){
// dark background with fade for trails
if(opts.showTrail){
ctx.fillStyle='rgba(2,13,26,0.35)';
}else{
ctx.fillStyle='rgba(2,13,26,1)';
}ctx.fillRect(0,0,W,H);
stepBoids();
for(let i=0;i<boids.length;i++) drawBoid(boids[i],i);
// predators
for(const p of predators){
p.pulse=(p.pulse+0.05)%(Math.PI*2);
const r=12+Math.sin(p.pulse)*4;
ctx.beginPath();
ctx.arc(p.x,p.y,r,0,Math.PI*2);
ctx.fillStyle='rgba(231,76,60,0.15)';
ctx.fill();
ctx.beginPath();
ctx.arc(p.x,p.y,8,0,Math.PI*2);
ctx.fillStyle='#e74c3c';
ctx.shadowColor='#e74c3c';
ctx.shadowBlur=12;
ctx.fill();
ctx.shadowBlur=0;
}// attractors
for(const a of attractors){
a.pulse=(a.pulse+0.04)%(Math.PI*2);
const r=12+Math.sin(a.pulse)*4;
ctx.beginPath();
ctx.arc(a.x,a.y,r,0,Math.PI*2);
ctx.fillStyle='rgba(0,184,148,0.15)';
ctx.fill();
ctx.beginPath();
ctx.arc(a.x,a.y,8,0,Math.PI*2);
ctx.fillStyle='#00b894';
ctx.shadowColor='#00b894';
ctx.shadowBlur=12;
ctx.fill();
ctx.shadowBlur=0;
}// stats update every 30 frames
frameCount++;
if(frameCount%30===0){
let totalSpd=0;
for(const b of boids){ totalSpd+=Math.sqrt(b.vx*b.vx+b.vy*b.vy); }
document.getElementById('statSpeed').textContent=(totalSpd/boids.length).toFixed(1);
document.getElementById('statClusters').textContent=estimateClusters();
}
requestAnimationFrame(draw);
}
// Canvas interaction
canvas.addEventListener('click',function(e){
const rect=canvas.getBoundingClientRect();
const scaleX=W/rect.width, scaleY=H/rect.height;
predators.push({x:(e.clientX-rect.left)*scaleX, y:(e.clientY-rect.top)*scaleY, pulse:0});
});
canvas.addEventListener('contextmenu',function(e){
e.preventDefault();
const rect=canvas.getBoundingClientRect();
const scaleX=W/rect.width, scaleY=H/rect.height;
attractors.push({x:(e.clientX-rect.left)*scaleX, y:(e.clientY-rect.top)*scaleY, pulse:0});
});
// Touch
let touchTimer=null;
canvas.addEventListener('touchstart',function(e){
e.preventDefault();
const rect=canvas.getBoundingClientRect();
const t=e.touches[0];
const scaleX=W/rect.width, scaleY=H/rect.height;
const tx=(t.clientX-rect.left)*scaleX, ty=(t.clientY-rect.top)*scaleY;
touchTimer=setTimeout(()=>{ attractors.push({x:tx,y:ty,pulse:0}); touchTimer=null; },400);
},{ passive:false });
canvas.addEventListener('touchend',function(e){
e.preventDefault();
if(touchTimer){ clearTimeout(touchTimer); touchTimer=null;
const rect=canvas.getBoundingClientRect();
const t=e.changedTouches[0];
const scaleX=W/rect.width, scaleY=H/rect.height;
predators.push({x:(t.clientX-rect.left)*scaleX, y:(t.clientY-rect.top)*scaleY, pulse:0});
}
},{ passive:false });
// Init sliders
['boidCount','maxSpeed','visualRange','sepWeight','aliWeight','cohWeight'].forEach(id=>{
const el=document.getElementById(id);
if(!el) return;
const min=parseFloat(el.min),max=parseFloat(el.max),v=parseFloat(el.value);
el.style.setProperty('--pct',((v-min)/(max-min)*100)+'%');
});
window.addEventListener('resize',()=>{ resizeCanvas(); });
resizeCanvas();
resetBoids();
draw();
</script>
<section class="explanation">
<h2>ボイドシミュレーターとは</h2>
<div class="chat">
<div class="cmsg st"><span class="ico">🙋</span>
<div class="bub">「ボイド」って何ですか?ただの点が動いてるだけに見えるけど、どうして魚の群れみたいな複雑な動きが生まれるんですか?</div>
</div>
<div class="cmsg pr"><span class="ico">🎓</span>
<div class="bub">大まかに言うと、各ボイドは「分離」「整列」「結合」のたった3つのルールに従って動く、賢い粒子なんだ。例えば、上のスライダーで「分離の重み」を0にしてみて。個体同士がぶつかり始めるでしょ?これが衝突回避の基本ルールだよ。</div>
</div>
<div class="cmsg st"><span class="ico">🙋</span>
<div class="bub">え、そうなんですか!でも、それぞれのルールの強さを変えると、動きが全然変わりますね。「整列」を強くすると軍隊みたいにピシッと揃う!これって実世界でも使われてるんですか?</div>
</div>
<div class="cmsg pr"><span class="ico">🎓</span>
<div class="bub">その通り!実務ではCGの群衆シーン生成や、ゲームのNPCの集団AIに使われることが多いよ。CAEの世界では、粒子法シミュレーションの基礎としても重要だ。例えば、自動車の塗装シミュレーションで、塗料の飛沫粒子が互いに干渉しないように動くモデルに応用されたりするんだ。</div>
</div>
<div class="cmsg st"><span class="ico">🙋</span>
<div class="bub">CAEにも関係あるんですね!「視野半径」を非常に小さくすると、群れがバラバラになっちゃいました。これって、現実の生物でも視界が悪いと群れを維持できないということですか?</div>
</div>
<div class="cmsg pr"><span class="ico">🎓</span>
<div class="bub">鋭いね!その通り、局所的な情報だけで大域的な秩序が生まれるのが「創発」の面白さだ。現場で多いのは、このアルゴリズムを拡張して、障害物回避のルールを追加したモデルだよ。パラメータをいじることで、自然な群れから整然とした隊列まで、様々な集団行動を再現できるんだ。</div>
</div>
</div>
<h2>よくある質問</h2>
<div class="faq-section">
<div class="faq-item">
<button class="faq-q" onclick="toggleFaq(this)">分離・整列・結合の重みを変更すると、具体的に群れの動きはどう変わりますか? <span class="arrow"><i class="fas fa-chevron-down"></i></span></button>
<div class="faq-a">分離の重みを大きくすると個体間の距離が広がりバラバラになりやすく、整列を大きくすると一方向に揃った動きになります。結合を強めると密集した塊になりやすく、逆に弱めると群れが拡散します。</div>
</div>
<div class="faq-item">
<button class="faq-q" onclick="toggleFaq(this)">視野半径Rはどのような影響を与えますか? <span class="arrow"><i class="fas fa-chevron-down"></i></span></button>
<div class="faq-a">視野半径が小さいと各ボイドは近くの個体しか認識せず、群れが分裂しやすくなります。逆に大きいと遠くの個体の影響も受けるため、全体としてまとまりやすい大きな群れが形成されます。</div>
</div>
<div class="faq-item">
<button class="faq-q" onclick="toggleFaq(this)">シミュレーションが途中で止まったり、ボイドが画面外に出てしまうのはなぜですか? <span class="arrow"><i class="fas fa-chevron-down"></i></span></button>
<div class="faq-a">境界条件が設定されていない場合、ボイドが画面外へ飛び出します。また、分離の重みが極端に小さいと衝突が発生し、計算が不安定になることがあります。必要に応じて反復境界や壁での反射を有効にしてください。</div>
</div>
<div class="faq-item">
<button class="faq-q" onclick="toggleFaq(this)">このシミュレーターは実際の魚や鳥の研究に使えますか? <span class="arrow"><i class="fas fa-chevron-down"></i></span></button>
<div class="faq-a">基本的な創発原理を理解するための教育・デモ用途に適しています。実際の生物の複雑な行動(障害物回避や捕食者応答など)を再現するには、追加のルールやパラメータ調整が必要です。</div>
</div>
</div>
<h2>実世界での応用</h2>
<div class="fblock">
<p><strong>コンピュータグラフィックス・ゲーム開発:</strong>映画やゲームで、自然な鳥の群れ、魚の群れ、あるいは群衆(ゾンビの大群など)のアニメーションを生成するために広く利用されています。個々のエージェントに複雑なAIを組み込む必要がなく、効率的に大規模な群集シーンを作成できます。</p>
<p><strong>群ロボット工学:</strong>多数の自律ロボットを協調させて動作させる制御アルゴリズムの基礎として応用されます。例えば、災害現場での探索や、倉庫内での物品運搬を、ロボットの群れで行う研究が進められています。</p>
<p><strong>粒子法シミュレーション(CAE):</strong>SPH(Smoothed Particle Hydrodynamics)法などの粒子ベースの流体解析では、粒子間の局所的な相互作用を計算します。ボイドアルゴリズムは、こうした粒子間の近傍探索と力の計算のプロトタイプとして教育上有効です。</p>
<p><strong>群知能アルゴリズム(PSO):</strong>「粒子群最適化(Particle Swarm Optimization)」は、ボイドの概念を最適化問題に応用したものです。探索空間内の粒子(解の候補)が、自身の最良解と群れ全体の最良解の情報を共有しながら最適解に向かって移動し、工学的な設計最適化などに用いられます。</p>
</div>
<h2>よくある誤解と注意点</h2>
<div class="fblock">
<p>まず、このシミュレーターを触り始めてよくあるのが「<strong>パラメータを極端にすればするほどリアルになる</strong>」という思い込みだ。例えば、分離・整列・結合の重みを全て最大にすると、かえって不自然な痙攣のような動きになる。実務では、生物の観察データや目的に応じて<strong>バランスが命</strong>。魚の群れを再現したいなら「整列」をやや強め、鳥の群れなら「結合」を少し弱めると良いことが多い。初期値のバランス(例えば 分離:1.5, 整列:1.0, 結合:1.0)を基準に微調整するのがコツだ。</p>
<p>次に、<strong>「視野半径」と「最大速度」の関係を見落とす</strong>ミス。視野半径を広げても、個体の最大速度が低すぎると、情報は得られてもそれに反応して動き切れない。逆に速度が速すぎると制御が効かず群れが崩壊する。例えば、視野半径を50ピクセルに設定したら、最大速度はその5〜10%程度(2.5〜5.0ピクセル/ステップ)から始めて調整すると安定しやすい。</p>
<p>最後に、実装上の落とし穴。<strong>距離計算のコストを甘く見ないこと</strong>。素直に全粒子間の距離を毎ステップ計算すると、粒子数Nが1000を超えたあたりから急激に重くなる。実務では空間分割(空間グリッド法や四分木)で近傍探索を最適化するのが必須だ。また、力の計算時に隣人の数|N|が0の場合(誰も視野内にいない)の除算エラーにも要注意。その場合は各力をゼロベクトルとして扱うなどの処理が必要になる。</p>
</div>
</section>
<!-- codex-static-related-tools:start -->
<section class="related-section static-related-tools" aria-label="関連ツール" data-related-source="boids-flocking.html" style="max-width:1100px;margin:32px auto 0;padding:0 24px">
<h2 style="font-size:1.05rem;line-height:1.35;margin:0 0 14px;color:#102033;display:flex;align-items:center;gap:8px"><i class="fas fa-diagram-project" style="color:#007BFF"></i>関連ツール</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px">
<a class="related-tool-card" href="/tools/game-of-life.html" style="display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;text-decoration:none;color:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04)">
<span style="width:34px;height:34px;border-radius:8px;background:#e6f2ff;color:#007BFF;display:grid;place-items:center;flex:0 0 auto"><i class="fas fa-seedling"></i></span>
<span style="display:grid;gap:4px;min-width:0">
<strong style="font-size:.9rem;line-height:1.35;color:#102033">コンウェイのライフゲーム</strong>
<span style="font-size:.75rem;line-height:1.45;color:#5f6b7a">コンウェイのライフゲームの無料シミュレーターです。ブラウザで直感的に操作できるセルオートマトンシミュレーターツールで、グライダーやパルサーなどの複雑なパターンを簡単に作成・観察できま...</span>
</span>
</a>
<a class="related-tool-card" href="/tools/autocorrelation.html" style="display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;text-decoration:none;color:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04)">
<span style="width:34px;height:34px;border-radius:8px;background:#e6f2ff;color:#007BFF;display:grid;place-items:center;flex:0 0 auto"><i class="fas fa-atom"></i></span>
<span style="display:grid;gap:4px;min-width:0">
<strong style="font-size:.9rem;line-height:1.35;color:#102033">自己相関関数シミュレーター — 周期検出</strong>
<span style="font-size:.75rem;line-height:1.45;color:#5f6b7a">自己相関関数シミュレーターはバイアス自己相関 R_xx[k] を計算し、ノイズに埋もれた周期信号の周期を検出する仕組みを可視化します。振幅・周波数・ノイズ・サンプル数を変えて、自己相...</span>
</span>
</a>
<a class="related-tool-card" href="/tools/ball-physics.html" style="display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;text-decoration:none;color:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04)">
<span style="width:34px;height:34px;border-radius:8px;background:#e6f2ff;color:#007BFF;display:grid;place-items:center;flex:0 0 auto"><i class="fas fa-baseball"></i></span>
<span style="display:grid;gap:4px;min-width:0">
<strong style="font-size:.9rem;line-height:1.35;color:#102033">球の運動シミュレーター</strong>
<span style="font-size:.75rem;line-height:1.45;color:#5f6b7a">球の運動シミュレーターで物理を楽しく学ぼう。重力や摩擦係数をスライダーで調整し、リアルタイムで球の動きをシミュレーション。直感的な操作で高校物理の力学が理解できる無料オンライン計算機...</span>
</span>
</a>
<a class="related-tool-card" href="/tools/betz-limit.html" style="display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;text-decoration:none;color:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04)">
<span style="width:34px;height:34px;border-radius:8px;background:#e6f2ff;color:#007BFF;display:grid;place-items:center;flex:0 0 auto"><i class="fas fa-atom"></i></span>
<span style="display:grid;gap:4px;min-width:0">
<strong style="font-size:.9rem;line-height:1.35;color:#102033">ベッツ限界 シミュレーター — 風車の理論最大効率</strong>
<span style="font-size:.75rem;line-height:1.45;color:#5f6b7a">風車のベッツ限界シミュレーター。風速・ローター直径・軸方向誘導係数 a・空気密度を変えて、パワー係数 C_P(a) = 4a(1-a)² と取り出し動力をリアルタイムに可視化。最適 ...</span>
</span>
</a>
<a class="related-tool-card" href="/tools/bmi-body-composition.html" style="display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;text-decoration:none;color:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04)">
<span style="width:34px;height:34px;border-radius:8px;background:#e6f2ff;color:#007BFF;display:grid;place-items:center;flex:0 0 auto"><i class="fas fa-person"></i></span>
<span style="display:grid;gap:4px;min-width:0">
<strong style="font-size:.9rem;line-height:1.35;color:#102033">BMI・体組成シミュレーター</strong>
<span style="font-size:.75rem;line-height:1.45;color:#5f6b7a">身長・体重からBMIを計算し、理想体重・標準体重・肥満度を可視化するシミュレーター。年齢・性別による体脂肪率推定と健康リスク判定も提供する無料ヘルスケアツール。</span>
</span>
</a>
<a class="related-tool-card" href="/tools/bone-fracture-risk.html" style="display:flex;gap:12px;align-items:flex-start;padding:14px;border:1px solid #e2e8f0;border-radius:8px;background:#fff;text-decoration:none;color:inherit;box-shadow:0 1px 2px rgba(15,23,42,.04)">
<span style="width:34px;height:34px;border-radius:8px;background:#e6f2ff;color:#007BFF;display:grid;place-items:center;flex:0 0 auto"><i class="fas fa-person-walking"></i></span>
<span style="display:grid;gap:4px;min-width:0">
<strong style="font-size:.9rem;line-height:1.35;color:#102033">骨折リスク・骨強度 計算機</strong>
<span style="font-size:.75rem;line-height:1.45;color:#5f6b7a">皮質骨の断面諸量から曲げ・圧縮・ねじり応力と安全率をリアルタイム計算。S-N疲労曲線による寿命予測、骨粗しょう症のTスコア補正、10年骨折リスクの可視化を統合した、骨強度評価のための...</span>
</span>
</a>
</div>
</section>
<!-- codex-static-related-tools:end -->
<div class="howto-card howto-guide">
<h3>使い方ガイド</h3>
<ol>
<li>ボイド数(boidCount)を1~500の範囲で設定。魚群シミュレーションでは150~200個が標準的な群集動作を示します</li>
<li>視認範囲(visualRange)を5~100ピクセルで調整。範囲が狭いほど局所的な相互作用が強まり、クラスター化が加速します</li>
<li>分離重み(sepWeight)と速度パラメータ(maxSpeed:2~8m/s相当)を同時に変更し、群れの凝集度と流動性のバランスを観察してください</li>
</ol>
</div>
<div class="howto-card howto-example">
<h3>具体的な計算例</h3>
<p>航空機編隊形成のシミュレーション:ボイド数50、視認範囲60ピクセル、最大速度6.5m/s(実機ではマッハ0.2相当)、分離重み0.8で実行した場合、平均速度4.2m/sで3~4個の安定したクラスターが形成されます。分離重みを1.5に増加させると、個体間距離が平均8.3ピクセル拡大し、クラスター数は7個に分散。一方、整列ルール優位(重み3.0以上)では編隊が一体化し、平均速度が5.9m/sまで上昇します</p>
</div>
<div class="howto-card howto-notes">
<h3>実務での注意点</h3>
<ul>
<li>分離距離(sepVal)が視認範囲を超える場合、衝突回避が機能せず密集状態が発生。ドローン群制御では最小2.5~3.0ピクセルの安全マージンを確保してください</li>
<li>ボイド数が300を超える場合、計算負荷により平均フレームレートが低下。リアルタイム性が必要な場合は150個以下を推奨</li>
<li>交差流環境(物体回避が必要)では視認範囲を20~40ピクセルに制限し、局所反応型の動作に限定することで群れの安定性が向上します</li>
</ul>
</div>
<footer style="background:#001F3F;color:rgba(255,255,255,.7);padding:24px;margin-top:32px;text-align:center">
<div style="max-width:1200px;margin:0 auto">
<div style="font-weight:700;color:#fff;margin-bottom:8px">NovaSolver Project</div>
<div style="display:flex;gap:16px;justify-content:center;flex-wrap:wrap;margin-bottom:8px;font-size:.78rem">
<a href="/tools/" style="color:rgba(255,255,255,.7);text-decoration:none">ライブラリ</a>
<a href="/index.html" style="color:rgba(255,255,255,.7);text-decoration:none">トップ</a>
<a href="/sitemap.html" style="color:rgba(255,255,255,.7);text-decoration:none">サイトマップ</a></div><div style="display:flex;gap:16px;justify-content:center;flex-wrap:wrap;margin-bottom:8px;font-size:.78rem"><a href="/about/author.html" style="color:rgba(255,255,255,.7);text-decoration:none">著者情報</a>
</div>
<div style="font-size:.65rem;color:rgba(255,255,255,.35)">© 2026 NovaSolver Contributors. All rights reserved.</div>
</div>
</footer>
<script src="/assets/sidebar-inject.v9.js" defer></script>
<script src="/tools/param-edit.js" defer></script>
<script src="/tools/share-state.js?v20260510r6" defer></script>
<script src="/tools/collapsible-cards.js" defer></script>
<script src="/tools/tooltips.js" defer></script>
<script src="/tools/tool-embed-share.js" defer></script>
<script src="/tools/related-tools.js" defer></script>
<script src="/tools/tool-objectives.js" defer></script>
<script defer src="/tools/tool-ux-template.js?v20260517r4"></script>
</body>
</html>