Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py
|
2 |
+
import streamlit as st
|
3 |
+
import numpy as np
|
4 |
+
import plotly.graph_objs as go
|
5 |
+
from dataclasses import dataclass
|
6 |
+
import time
|
7 |
+
|
8 |
+
# カスタムコンポーネントのインポート
|
9 |
+
from my_mouse_tracker import my_mouse_tracker
|
10 |
+
|
11 |
+
@dataclass
|
12 |
+
class Boid:
|
13 |
+
position: np.ndarray
|
14 |
+
velocity: np.ndarray
|
15 |
+
|
16 |
+
def initialize_boids(num_boids, width, height, speed):
|
17 |
+
boids = []
|
18 |
+
for _ in range(num_boids):
|
19 |
+
position = np.array([np.random.uniform(0, width), np.random.uniform(0, height)])
|
20 |
+
angle = np.random.uniform(0, 2*np.pi)
|
21 |
+
velocity = np.array([np.cos(angle), np.sin(angle)]) * speed
|
22 |
+
boids.append(Boid(position, velocity))
|
23 |
+
return boids
|
24 |
+
|
25 |
+
def update_boids(boids, width, height, alignment_factor, cohesion_factor, separation_factor,
|
26 |
+
perception_radius, max_speed, mouse_pos=None, mouse_influence=0.05):
|
27 |
+
new_boids = []
|
28 |
+
for boid in boids:
|
29 |
+
alignment = np.zeros(2)
|
30 |
+
cohesion = np.zeros(2)
|
31 |
+
separation = np.zeros(2)
|
32 |
+
total = 0
|
33 |
+
for other in boids:
|
34 |
+
distance = np.linalg.norm(other.position - boid.position)
|
35 |
+
if other != boid and distance < perception_radius:
|
36 |
+
alignment += other.velocity
|
37 |
+
cohesion += other.position
|
38 |
+
separation += (boid.position - other.position) / distance
|
39 |
+
total += 1
|
40 |
+
if total > 0:
|
41 |
+
alignment = (alignment / total) * alignment_factor
|
42 |
+
cohesion = ((cohesion / total) - boid.position) * cohesion_factor
|
43 |
+
separation = (separation / total) * separation_factor
|
44 |
+
boid.velocity += alignment + cohesion + separation
|
45 |
+
# マウスの影響
|
46 |
+
if mouse_pos is not None:
|
47 |
+
direction_to_mouse = mouse_pos - boid.position
|
48 |
+
distance_to_mouse = np.linalg.norm(direction_to_mouse)
|
49 |
+
if distance_to_mouse < perception_radius:
|
50 |
+
# 避ける動作
|
51 |
+
boid.velocity -= (direction_to_mouse / distance_to_mouse) * mouse_influence
|
52 |
+
# 速度の制限
|
53 |
+
speed = np.linalg.norm(boid.velocity)
|
54 |
+
if speed > max_speed:
|
55 |
+
boid.velocity = (boid.velocity / speed) * max_speed
|
56 |
+
# 位置の更新
|
57 |
+
boid.position += boid.velocity
|
58 |
+
# 境界条件(トーラス型)
|
59 |
+
boid.position[0] %= width
|
60 |
+
boid.position[1] %= height
|
61 |
+
new_boids.append(boid)
|
62 |
+
return new_boids
|
63 |
+
|
64 |
+
def plot_boids(boids, width, height, mouse_pos=None):
|
65 |
+
x = [boid.position[0] for boid in boids]
|
66 |
+
y = [boid.position[1] for boid in boids]
|
67 |
+
trace = go.Scatter(
|
68 |
+
x=x,
|
69 |
+
y=y,
|
70 |
+
mode='markers',
|
71 |
+
marker=dict(size=8, color='blue'),
|
72 |
+
)
|
73 |
+
layout = go.Layout(
|
74 |
+
xaxis=dict(range=[0, width], autorange=False, zeroline=False, showgrid=False),
|
75 |
+
yaxis=dict(range=[0, height], autorange=False, zeroline=False, showgrid=False),
|
76 |
+
margin=dict(l=0, r=0, t=0, b=0),
|
77 |
+
height=600,
|
78 |
+
width=600,
|
79 |
+
shapes=[],
|
80 |
+
)
|
81 |
+
# マウス位置の表示
|
82 |
+
if mouse_pos is not None:
|
83 |
+
layout['shapes'] = [
|
84 |
+
dict(
|
85 |
+
type="circle",
|
86 |
+
xref="x",
|
87 |
+
yref="y",
|
88 |
+
x0=mouse_pos[0]-10,
|
89 |
+
y0=mouse_pos[1]-10,
|
90 |
+
x1=mouse_pos[0]+10,
|
91 |
+
y1=mouse_pos[1]+10,
|
92 |
+
line=dict(color="red"),
|
93 |
+
)
|
94 |
+
]
|
95 |
+
fig = go.Figure(data=[trace], layout=layout)
|
96 |
+
return fig
|
97 |
+
|
98 |
+
def main():
|
99 |
+
st.set_page_config(page_title="Boidsシミュレーション", layout="wide")
|
100 |
+
st.title("インタラクティブBoidsシミュレーション")
|
101 |
+
st.markdown("マウスの位置に反応する魚の群れをシミュレートします。")
|
102 |
+
|
103 |
+
# サイドバーのパラメータ設定
|
104 |
+
st.sidebar.header("パラメータ設定")
|
105 |
+
num_boids = st.sidebar.slider("ボイドの数", 10, 500, 100)
|
106 |
+
width = st.sidebar.slider("キャンバスの幅", 300, 1200, 800)
|
107 |
+
height = st.sidebar.slider("キャンバスの高さ", 300, 1200, 800)
|
108 |
+
speed = st.sidebar.slider("初期速度", 0.5, 5.0, 2.0)
|
109 |
+
alignment_factor = st.sidebar.slider("整列係数", 0.0, 2.0, 1.0)
|
110 |
+
cohesion_factor = st.sidebar.slider("結合係数", 0.0, 2.0, 1.0)
|
111 |
+
separation_factor = st.sidebar.slider("分離係数", 0.0, 2.0, 1.5)
|
112 |
+
perception_radius = st.sidebar.slider("認識半径", 10, 200, 50)
|
113 |
+
max_speed = st.sidebar.slider("最大速度", 1.0, 10.0, 4.0)
|
114 |
+
simulation_speed = st.sidebar.slider("シミュレーション速度(秒)", 0.01, 1.0, 0.1)
|
115 |
+
mouse_influence = st.sidebar.slider("マウスの影響力", 0.0, 1.0, 0.05)
|
116 |
+
|
117 |
+
# マウス位置の取得
|
118 |
+
mouse_event = my_mouse_tracker()
|
119 |
+
if mouse_event:
|
120 |
+
mouse_x, mouse_y = mouse_event['x'], mouse_event['y']
|
121 |
+
# ウィンドウサイズとキャンバスサイズをマッピング
|
122 |
+
# ここでは仮にウィンドウサイズがキャンバスサイズと同じと���定
|
123 |
+
mouse_pos = np.array([mouse_x, mouse_y])
|
124 |
+
else:
|
125 |
+
mouse_pos = None
|
126 |
+
|
127 |
+
# セッションステートの初期化
|
128 |
+
if 'boids' not in st.session_state:
|
129 |
+
st.session_state.boids = initialize_boids(num_boids, width, height, speed)
|
130 |
+
st.session_state.last_update = time.time()
|
131 |
+
|
132 |
+
# パラメータの変更時にボイドを再初期化
|
133 |
+
current_params = (num_boids, width, height, speed)
|
134 |
+
if st.session_state.get('params', None) != current_params:
|
135 |
+
st.session_state.boids = initialize_boids(num_boids, width, height, speed)
|
136 |
+
st.session_state.last_update = time.time()
|
137 |
+
st.session_state.params = current_params
|
138 |
+
|
139 |
+
# シミュレーションの更新
|
140 |
+
current_time = time.time()
|
141 |
+
if current_time - st.session_state.last_update > simulation_speed:
|
142 |
+
st.session_state.boids = update_boids(
|
143 |
+
st.session_state.boids, width, height,
|
144 |
+
alignment_factor, cohesion_factor,
|
145 |
+
separation_factor, perception_radius,
|
146 |
+
max_speed, mouse_pos, mouse_influence
|
147 |
+
)
|
148 |
+
st.session_state.last_update = current_time
|
149 |
+
|
150 |
+
# ビジュアライゼーションの表示
|
151 |
+
fig = plot_boids(st.session_state.boids, width, height, mouse_pos)
|
152 |
+
st.plotly_chart(fig, use_container_width=True)
|
153 |
+
|
154 |
+
# 自動リロード
|
155 |
+
st.experimental_rerun()
|
156 |
+
|
157 |
+
if __name__ == "__main__":
|
158 |
+
main()
|