Spaces:
Sleeping
Sleeping
| import math, random, time | |
| from dataclasses import dataclass, field | |
| from typing import List, Optional, Tuple | |
| import gradio as gr | |
| from PIL import Image, ImageDraw | |
| W, H = 900, 540 | |
| ROOM = (60, 40, W - 60, H - 40) | |
| DT = 0.05 | |
| MAX_SPEED_BASE = 65.0 | |
| PERSON_RADIUS = 6 | |
| EXIT_W, EXIT_H = 28, 68 | |
| C_BG = (245, 246, 248, 255) | |
| C_ROOM = (250, 250, 250, 255) | |
| C_BORDER = (28, 28, 28, 255) | |
| C_OBS = (175, 178, 185, 255) | |
| C_EXIT = (52, 132, 255, 255) | |
| C_CALM = (44, 184, 78, 255) | |
| C_ALERT = (250, 191, 35, 255) | |
| C_PANIC = (233, 68, 68, 255) | |
| C_INCIDENT = (230, 60, 60, 96) | |
| RUNNING = False | |
| class Person: | |
| x: float; y: float; vx: float; vy: float | |
| stress: float = 0.0; state: int = 0; evacuated: bool = False | |
| def color(self): return [C_CALM, C_ALERT, C_PANIC][self.state] | |
| class ExitZone: | |
| x: int; y: int; w: int = EXIT_W; h: int = EXIT_H | |
| def rect(self): return (self.x, self.y, self.x + self.w, self.y + self.h) | |
| class World: | |
| width: int = W; height: int = H; room: Tuple[int,int,int,int] = ROOM | |
| people: List[Person] = field(default_factory=list) | |
| exits: List[ExitZone] = field(default_factory=list) | |
| incident: Optional[Tuple[int,int,int]] = None | |
| speed_scale: float = 1.0; cohesion: float = 0.3; separation: float = 0.6 | |
| panic_spread: float = 2.0; personal_space: float = 22.0 | |
| def step(self, dt: float): | |
| def nearest_exit_vec(px, py): | |
| best=None; best_d2=1e12 | |
| for ex in self.exits: | |
| cx, cy = ex.x + ex.w/2, ex.y + ex.h/2 | |
| dx, dy = cx - px, cy - py | |
| d2 = dx*dx + dy*dy | |
| if d2 < best_d2: best_d2, best = d2, (dx, dy) | |
| return best or (0.0, 0.0) | |
| for p in self.people: | |
| if p.evacuated: continue | |
| gx, gy = nearest_exit_vec(p.x, p.y) | |
| p.vx += gx*0.002; p.vy += gy*0.002 | |
| v = math.hypot(p.vx, p.vy) | |
| vmax = MAX_SPEED_BASE*self.speed_scale | |
| if v > vmax: p.vx, p.vy = p.vx/v*vmax, p.vy/v*vmax | |
| p.x += p.vx*dt; p.y += p.vy*dt | |
| for ex in self.exits: | |
| x0, y0, x1, y1 = ex.rect() | |
| if x0 <= p.x <= x1 and y0 <= p.y <= y1: | |
| p.evacuated = True | |
| def render(self): | |
| img = Image.new("RGBA", (self.width, self.height), C_BG) | |
| d = ImageDraw.Draw(img, "RGBA") | |
| d.rectangle(self.room, fill=C_ROOM, outline=C_BORDER, width=3) | |
| for ex in self.exits: | |
| d.rectangle(ex.rect(), fill=C_EXIT) | |
| d.text((ex.x+6, ex.y+ex.h/2-7), "Exit", fill=(255,255,255,255)) | |
| for p in self.people: | |
| if not p.evacuated: | |
| d.ellipse((p.x-4, p.y-4, p.x+4, p.y+4), fill=p.color()) | |
| return img | |
| def make_world(n_people=200): | |
| w = World() | |
| ex1 = ExitZone(x=ROOM[2]-8-EXIT_W, y=int(ROOM[1]+70)) | |
| ex2 = ExitZone(x=ROOM[2]-8-EXIT_W, y=int(ROOM[3]-70-EXIT_H)) | |
| w.exits = [ex1, ex2] | |
| for _ in range(n_people): | |
| w.people.append(Person(random.randint(100, 700), random.randint(100, 400), 0, 0)) | |
| return w | |
| def reset_fn(n_people): | |
| w = make_world(int(n_people)) | |
| return w.render(), w | |
| def start_fn(world): | |
| global RUNNING; RUNNING = True | |
| while RUNNING: | |
| world.step(DT) | |
| yield world.render(), world | |
| time.sleep(DT) | |
| def pause_fn(): | |
| global RUNNING; RUNNING = False | |
| return gr.update(), gr.update() | |
| with gr.Blocks(title="Behavioral ML in Virtual Crowds") as demo: | |
| gr.Markdown("## Behavioral ML in Virtual Crowds · eenvoudige simulatie") | |
| img = gr.Image(label="Simulatie") | |
| n_people = gr.Slider(10, 500, value=200, step=10, label="Aantal mensen") | |
| start_btn = gr.Button("▶️ Start") | |
| pause_btn = gr.Button("⏸️ Pauze") | |
| reset_btn = gr.Button("🔁 Reset") | |
| state = gr.State() | |
| reset_btn.click(reset_fn, [n_people], [img, state]) | |
| start_btn.click(start_fn, [state], [img, state]) | |
| pause_btn.click(pause_fn, outputs=[img, state]) | |
| if __name__ == "__main__": | |
| demo.launch() | |