Repository navigation
Expand file tree
/
Copy pathflocking_gpu.py
More file actions
148 lines (120 loc) · 4.64 KB
/
Copy pathflocking_gpu.py
File metadata and controls
148 lines (120 loc) · 4.64 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
# GPU flocking: the boids from flocking.py, built from generic particle ops
# over each boid's neighbors. Nothing is read back to the CPU.
from mewnala import *
from math import cos, sin
from random import uniform
BOID_COUNT = 100000
BOUND = 30.0 # half-extent of the wrapping box
NEIGHBOR_DIST = 5.0
SEPARATION_DIST = 2.5
MAX_SPEED = 10.0 # units per second
MAX_FORCE = 6.0 # units per second²
DT = 1.0 / 60.0
# Separation steers away from the summed offsets, so its speed is negative.
RULES = [
("separation", -MAX_SPEED, 1.5, "close"),
("alignment", MAX_SPEED, 1.0, "near"),
("cohesion", MAX_SPEED, 1.0, "near"),
]
p = None
boid = None
mat = None
grid = None
title_last_time = 0.0
title_last_frame = 0
# Two triangles folded slightly along the nose-tail spine, like a paper
# boid pointing down +Z. The fold keeps the boid visible edge-on and gives
# each wing its own normal, so the flock glints as it banks.
def boid_geometry(half_width, length, droop):
g = create_geometry()
n = (half_width * half_width + droop * droop) ** 0.5
nose = (0.0, 0.0, length * 0.5)
tail = (0.0, 0.0, -length * 0.5)
g.normal(-droop / n, half_width / n, 0.0)
g.vertex(*nose)
g.vertex(-half_width, -droop, -length * 0.5)
g.vertex(*tail)
g.normal(droop / n, half_width / n, 0.0)
g.vertex(*nose)
g.vertex(*tail)
g.vertex(half_width, -droop, -length * 0.5)
for i in range(6):
g.index(i)
return g
def setup():
global p, boid, mat, grid
size(900, 700)
window_title(f"GPU Flocking — {BOID_COUNT:,} boids")
mode_3d()
directional_light((0.95, 0.9, 0.85), 800.0)
p = create_particles(BOID_COUNT)
positions = []
velocities = []
rotations = []
colors = []
for _ in range(BOID_COUNT):
positions.append([uniform(-BOUND, BOUND) for _ in range(3)])
velocities.append([uniform(-1.0, 1.0) * MAX_SPEED * 0.4 for _ in range(3)])
rotations.append([0.0, 0.0, 0.0, 1.0])
c = hsva(uniform(190.0, 280.0), 0.7, 1.0)
colors.append([c.r, c.g, c.b, 1.0])
p.buffer("position").write(positions)
p.buffer("rotation").write(rotations)
p.buffer("velocity").write(velocities)
color_buf = p.buffer("color")
color_buf.write(colors)
boid = boid_geometry(0.4, 1.3, 0.15)
mat = create_material(albedo=color_buf)
cells = int((2.0 * BOUND) / NEIGHBOR_DIST) + 1
grid = p.create_grid(
min=[-BOUND, -BOUND, -BOUND],
cell_size=NEIGHBOR_DIST,
dims=[cells, cells, cells],
)
# Reynolds steering. `mask` zeroes it for boids with no neighbors in range,
# which would otherwise brake.
def steer(desired, speed, mask):
p.apply(MAP, desired, op=NORMALIZE, length=speed)
p.apply(COMBINE, desired, "velocity", op=SUB)
p.apply(MAP, desired, op=LIMIT, max_length=MAX_FORCE * DT)
p.apply(COMBINE, desired, mask, op=MUL)
def flock():
p.apply(FIND_NEIGHBORS, grid=grid, radius=NEIGHBOR_DIST)
# what each rule steers toward, summed over the neighbors
p.apply(NEIGHBOR, "position", out="separation", op=SUM,
relative=True, radius=SEPARATION_DIST, falloff=INVERSE_SQUARE)
p.apply(NEIGHBOR, "velocity", out="alignment", op=SUM)
p.apply(NEIGHBOR, "position", out="cohesion", op=SUM, relative=True)
# which boids have any neighbors for each rule
p.apply(NEIGHBOR, out="close", op=COUNT, radius=SEPARATION_DIST)
p.apply(NEIGHBOR, out="near", op=COUNT)
p.apply(MAP, "close", op=GREATER, threshold=0)
p.apply(MAP, "near", op=GREATER, threshold=0)
for i, (rule, speed, weight, mask) in enumerate(RULES):
steer(rule, speed, mask)
if i == 0:
p.apply(MAP, rule, out="force", op=AFFINE, scale=weight)
else:
p.apply(COMBINE, "force", rule, op="add", b_scale=weight)
p.apply(COMBINE, "velocity", "force", op="add")
p.apply(MAP, "velocity", op=LIMIT, min_length=MAX_SPEED * 0.25, max_length=MAX_SPEED)
def draw():
global title_last_time, title_last_frame
title_elapsed = elapsed_time - title_last_time
if title_elapsed >= 0.5:
fps = (frame_count - title_last_frame) / title_elapsed
window_title(f"GPU Flocking — {BOID_COUNT:,} boids — {fps:.0f} FPS")
title_last_time = elapsed_time
title_last_frame = frame_count
t = elapsed_time * 0.1
r = BOUND * 2.6
camera_position(cos(t) * r, BOUND * 0.8, sin(t) * r)
camera_look_at(0.0, 0.0, 0.0)
background(10, 12, 18)
flock()
material(mat)
particles(p, boid)
p.apply(INTEGRATE, dt=DT)
p.apply(BOUNDS_BOX, aabb_min=[-BOUND] * 3, aabb_max=[BOUND] * 3, mode=2)
p.apply(ORIENT, forward=[0.0, 0.0, 1.0], up=[0.0, 1.0, 0.0])
run()