Spaces:
Sleeping
Sleeping
# app.py | |
import streamlit as st | |
import numpy as np | |
import plotly.graph_objs as go | |
from dataclasses import dataclass | |
import time | |
# カスタムコンポーネントのインポート | |
from my_mouse_tracker import my_mouse_tracker | |
class Boid: | |
position: np.ndarray | |
velocity: np.ndarray | |
def initialize_boids(num_boids, width, height, speed): | |
boids = [] | |
for _ in range(num_boids): | |
position = np.array([np.random.uniform(0, width), np.random.uniform(0, height)]) | |
angle = np.random.uniform(0, 2*np.pi) | |
velocity = np.array([np.cos(angle), np.sin(angle)]) * speed | |
boids.append(Boid(position, velocity)) | |
return boids | |
def update_boids(boids, width, height, alignment_factor, cohesion_factor, separation_factor, | |
perception_radius, max_speed, mouse_pos=None, mouse_influence=0.05): | |
new_boids = [] | |
for boid in boids: | |
alignment = np.zeros(2) | |
cohesion = np.zeros(2) | |
separation = np.zeros(2) | |
total = 0 | |
for other in boids: | |
distance = np.linalg.norm(other.position - boid.position) | |
if other != boid and distance < perception_radius: | |
alignment += other.velocity | |
cohesion += other.position | |
separation += (boid.position - other.position) / distance | |
total += 1 | |
if total > 0: | |
alignment = (alignment / total) * alignment_factor | |
cohesion = ((cohesion / total) - boid.position) * cohesion_factor | |
separation = (separation / total) * separation_factor | |
boid.velocity += alignment + cohesion + separation | |
# マウスの影響 | |
if mouse_pos is not None: | |
direction_to_mouse = mouse_pos - boid.position | |
distance_to_mouse = np.linalg.norm(direction_to_mouse) | |
if distance_to_mouse < perception_radius: | |
# 避ける動作 | |
boid.velocity -= (direction_to_mouse / distance_to_mouse) * mouse_influence | |
# 速度の制限 | |
speed = np.linalg.norm(boid.velocity) | |
if speed > max_speed: | |
boid.velocity = (boid.velocity / speed) * max_speed | |
# 位置の更新 | |
boid.position += boid.velocity | |
# 境界条件(トーラス型) | |
boid.position[0] %= width | |
boid.position[1] %= height | |
new_boids.append(boid) | |
return new_boids | |
def plot_boids(boids, width, height, mouse_pos=None): | |
x = [boid.position[0] for boid in boids] | |
y = [boid.position[1] for boid in boids] | |
trace = go.Scatter( | |
x=x, | |
y=y, | |
mode='markers', | |
marker=dict(size=8, color='blue'), | |
) | |
layout = go.Layout( | |
xaxis=dict(range=[0, width], autorange=False, zeroline=False, showgrid=False), | |
yaxis=dict(range=[0, height], autorange=False, zeroline=False, showgrid=False), | |
margin=dict(l=0, r=0, t=0, b=0), | |
height=600, | |
width=600, | |
shapes=[], | |
) | |
# マウス位置の表示 | |
if mouse_pos is not None: | |
layout['shapes'] = [ | |
dict( | |
type="circle", | |
xref="x", | |
yref="y", | |
x0=mouse_pos[0]-10, | |
y0=mouse_pos[1]-10, | |
x1=mouse_pos[0]+10, | |
y1=mouse_pos[1]+10, | |
line=dict(color="red"), | |
) | |
] | |
fig = go.Figure(data=[trace], layout=layout) | |
return fig | |
def main(): | |
st.set_page_config(page_title="Boidsシミュレーション", layout="wide") | |
st.title("インタラクティブBoidsシミュレーション") | |
st.markdown("マウスの位置に反応する魚の群れをシミュレートします。") | |
# サイドバーのパラメータ設定 | |
st.sidebar.header("パラメータ設定") | |
num_boids = st.sidebar.slider("ボイドの数", 10, 500, 100) | |
width = st.sidebar.slider("キャンバスの幅", 300, 1200, 800) | |
height = st.sidebar.slider("キャンバスの高さ", 300, 1200, 800) | |
speed = st.sidebar.slider("初期速度", 0.5, 5.0, 2.0) | |
alignment_factor = st.sidebar.slider("整列係数", 0.0, 2.0, 1.0) | |
cohesion_factor = st.sidebar.slider("結合係数", 0.0, 2.0, 1.0) | |
separation_factor = st.sidebar.slider("分離係数", 0.0, 2.0, 1.5) | |
perception_radius = st.sidebar.slider("認識半径", 10, 200, 50) | |
max_speed = st.sidebar.slider("最大速度", 1.0, 10.0, 4.0) | |
simulation_speed = st.sidebar.slider("シミュレーション速度(秒)", 0.01, 1.0, 0.1) | |
mouse_influence = st.sidebar.slider("マウスの影響力", 0.0, 1.0, 0.05) | |
# マウス位置の取得 | |
mouse_event = my_mouse_tracker() | |
if mouse_event: | |
mouse_x, mouse_y = mouse_event['x'], mouse_event['y'] | |
# ウィンドウサイズとキャンバスサイズをマッピング | |
# ここでは仮にウィンドウサイズがキャンバスサイズと同じと仮定 | |
mouse_pos = np.array([mouse_x, mouse_y]) | |
else: | |
mouse_pos = None | |
# セッションステートの初期化 | |
if 'boids' not in st.session_state: | |
st.session_state.boids = initialize_boids(num_boids, width, height, speed) | |
st.session_state.last_update = time.time() | |
# パラメータの変更時にボイドを再初期化 | |
current_params = (num_boids, width, height, speed) | |
if st.session_state.get('params', None) != current_params: | |
st.session_state.boids = initialize_boids(num_boids, width, height, speed) | |
st.session_state.last_update = time.time() | |
st.session_state.params = current_params | |
# シミュレーションの更新 | |
current_time = time.time() | |
if current_time - st.session_state.last_update > simulation_speed: | |
st.session_state.boids = update_boids( | |
st.session_state.boids, width, height, | |
alignment_factor, cohesion_factor, | |
separation_factor, perception_radius, | |
max_speed, mouse_pos, mouse_influence | |
) | |
st.session_state.last_update = current_time | |
# ビジュアライゼーションの表示 | |
fig = plot_boids(st.session_state.boids, width, height, mouse_pos) | |
st.plotly_chart(fig, use_container_width=True) | |
# 自動リロード | |
st.experimental_rerun() | |
if __name__ == "__main__": | |
main() | |