# Flocking (cardumes e revoadas)
### Preâmbulo
- Vamos usar py5 pra variar ;-)
- https://abav.lugaralgum.com/material-aulas/
- https://py5coding.org
- Python Fluente https://pythonfluente.com/2/
### O assunto do dia
- Craig Reynolds (1986)
- https://en.wikipedia.org/wiki/Boids
- ver também Ichiro Aoki (1982)

- No Nature of Code
- https://natureofcode.com/autonomous-agents/#complex-systems
- Exemplo do Rohola em Python https://github.com/roholazandie/boids/tree/master
- Sketch-a-day com linhas https://github.com/villares/sketch-a-day/blob/main/2023/sketch_2023_10_31/sketch_2023_10_31.py
- Sketch-a-day com **Numpy**... https://github.com/villares/sketch-a-day/blob/main/2024/sketch_2024_01_19/sketch_2024_01_19.py
Brincadeira com mouse repelindo
```python=
import py5
from py5 import Py5Vector as V
boids = []
def setup():
py5.size(800, 600)
for i in range(30):
boids.append(Boid(py5.random(py5.width),
py5.random(py5.height)))
def draw():
py5.background(255, 0, 255)
# py5.fill(py5.random(255),
# py5.random(255),
# py5.random(255))
# py5.circle(py5.mouse_x, py5.mouse_y, 50)
mv = V(py5.mouse_x, py5.mouse_y)
for b in boids:
b.update(mv)
b.desenhar()
class Boid:
vel_maxima = 8
def __init__(self, x, y):
self.pos = V(x, y)
self.vel = V(0, 0)
self.acc = V(0, 0)
def apply_force(self, mouse_vector):
f = self.pos - mouse_vector
f.normalize()
return f * 0.001
def update(self, mouse_vector):
self.acc = self.apply_force(mouse_vector)
self.vel += self.acc
self.vel.set_limit(self.vel_maxima)
self.pos += self.vel
self.acc.set_mag(0)
self.pos.x = self.pos.x % py5.width
self.pos.y = self.pos.y % py5.height
def desenhar(self):
py5.circle(self.pos.x,
self.pos.y,
2 + self.vel.mag_sq * 2)
py5.run_sketch()
```
Primeira aproximação do flocking

```python=
import py5
from py5 import Py5Vector as V
import numpy as np
boids = []
def setup():
py5.size(800, 600)
for i in range(50):
boids.append(Boid(py5.random(py5.width),
py5.random(py5.height)))
def draw():
py5.background(255, 0, 255)
# py5.fill(py5.random(255),
# py5.random(255),
# py5.random(255))
# py5.circle(py5.mouse_x, py5.mouse_y, 50)
mv = V(py5.mouse_x, py5.mouse_y)
for b in boids:
b.update(mv)
b.desenhar()
class Boid:
vel_maxima = 8
forca_maxima = 0.3
perception = 100
def __init__(self, x, y):
self.pos = V(x, y)
self.vel = V(0, 0)
self.acc = V(0, 0)
def apply_force(self, mouse_V):
f = self.pos - mouse_V
f.normalize()
return f * 0.001
def update(self, mouse_V):
#self.acc = self.apply_force(mouse_V)
self.acc += self.align(boids)
self.acc += self.cohesion(boids)
self.acc += self.separation(boids)
self.vel += self.acc
self.vel.set_limit(self.vel_maxima)
self.pos += self.vel
self.acc.set_mag(0)
self.pos.x = self.pos.x % py5.width
self.pos.y = self.pos.y % py5.height
def desenhar(self):
py5.circle(self.pos.x,
self.pos.y,
10)
def align(self, boids):
steering = V(0, 0)
total = 0
avg_V = V(0, 0)
for boid in boids:
if boid != self and (boid.pos - self.pos).mag < self.perception:
avg_V += boid.vel
total += 1
if total > 0:
avg_V /= total
if avg_V.mag_sq > 0:
avg_V = avg_V.norm * self.vel_maxima
steering = avg_V - self.vel
return steering
def cohesion(self, boids):
steering = V(0, 0)
total = 0
center_of_mass = V(0, 0)
for boid in boids:
if boid != self and (boid.pos - self.pos).mag < self.perception:
center_of_mass += boid.pos
total += 1
if total > 0:
center_of_mass /= total
#center_of_mass = V(*center_of_mass)
vec_to_com = center_of_mass - self.pos
if vec_to_com.mag_sq > 0:
vec_to_com = vec_to_com.norm * self.vel_maxima
steering = vec_to_com - self.vel
steering.set_limit(self.forca_maxima)
return steering
def separation(self, boids):
steering = V(0, 0)
total = 0
avg_V = V(0, 0)
for boid in boids:
distance = (boid.pos - self.pos).mag
if self != boid and distance < self.perception:
diff = self.pos - boid.pos
diff /= distance
avg_V += diff
total += 1
if total > 0:
avg_V /= total
#avg_V = V(*avg_V)
if avg_V.mag_sq > 0:
avg_V = avg_V.norm * self.vel_maxima
steering = avg_V - self.vel
steering.set_limit(self.forca_maxima)
return steering
py5.run_sketch()
```
Exemplo com linhas
```python!
import py5
from py5 import Py5Vector as V
import numpy as np
boids = []
def setup():
py5.size(800, 600)
for i in range(50):
boids.append(Boid(py5.random(py5.width),
py5.random(py5.height)))
def draw():
py5.background(200)
mv = V(py5.mouse_x, py5.mouse_y)
py5.lines(b.update(mv) for b in boids)
py5.window_title(str(int(py5.get_frame_rate())))
class Boid:
vel_maxima = 8
forca_maxima = 0.3
perception = 100
def __init__(self, x, y):
self.pos = V(x, y)
self.vel = V(0, 0)
self.acc = V(0, 0)
def apply_force(self, mouse_V):
f = self.pos - mouse_V
f.normalize()
return f * 0.001
def update(self, mouse_V):
#self.acc = self.apply_force(mouse_V)
self.acc += self.align(boids)
self.acc += self.cohesion(boids)
self.acc += self.separation(boids)
self.vel += self.acc
self.vel.set_limit(self.vel_maxima)
self.pos += self.vel
self.acc.set_mag(0)
self.pos.x = self.pos.x % py5.width
self.pos.y = self.pos.y % py5.height
return ( # devolve coordenadas de uma linha
self.pos.x,
self.pos.y,
self.pos.x + self.vel.x * 2,
self.pos.y + self.vel.y * 2
)
def align(self, boids):
steering = V(0, 0)
total = 0
avg_V = V(0, 0)
for boid in boids:
if boid != self and (boid.pos - self.pos).mag_sq < self.perception ** 2:
avg_V += boid.vel
total += 1
if total > 0:
avg_V /= total
if avg_V.mag_sq > 0:
avg_V = avg_V.norm * self.vel_maxima
steering = avg_V - self.vel
return steering
def cohesion(self, boids):
steering = V(0, 0)
total = 0
center_of_mass = V(0, 0)
for boid in boids:
if boid != self and (boid.pos - self.pos).mag_sq < self.perception ** 2:
center_of_mass += boid.pos
total += 1
if total > 0:
center_of_mass /= total
#center_of_mass = V(*center_of_mass)
vec_to_com = center_of_mass - self.pos
if vec_to_com.mag_sq > 0:
vec_to_com = vec_to_com.norm * self.vel_maxima
steering = vec_to_com - self.vel
steering.set_limit(self.forca_maxima)
return steering
def separation(self, boids):
steering = V(0, 0)
total = 0
avg_V = V(0, 0)
for boid in boids:
distance = (boid.pos - self.pos).mag
if self != boid and distance < self.perception:
diff = self.pos - boid.pos
diff /= distance
avg_V += diff
total += 1
if total > 0:
avg_V /= total
#avg_V = V(*avg_V)
if avg_V.mag_sq > 0:
avg_V = avg_V.norm * self.vel_maxima
steering = avg_V - self.vel
steering.set_limit(self.forca_maxima)
return steering
py5.run_sketch()
```