Skip to content

Commit 9d972c4

Browse files
Made the simulation
0 parents  commit 9d972c4

7 files changed

Lines changed: 320 additions & 0 deletions

File tree

.idea/PythonProject.iml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/workspace.xml

Lines changed: 64 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sim.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import pygame
2+
import random
3+
import math
4+
import threading
5+
6+
7+
class Particle():
8+
particles = []
9+
densities = []
10+
smoothingRadius = 100
11+
12+
def __init__(self, pos, velocity, radius, color, mass=1):
13+
self.index = len(Particle.particles)
14+
self.pos = pos
15+
self.velocity = velocity
16+
self.radius = radius
17+
self.color = color
18+
self.mass = mass
19+
self.density = 0
20+
self.max_speed = 100
21+
Particle.particles.append(self)
22+
Particle.densities.append(self.density)
23+
24+
def update(self, screen, net_ext_force, dt):
25+
self.update_densities()
26+
27+
self.velocity.x += net_ext_force.x / self.mass * dt
28+
self.velocity.y += net_ext_force.y / self.mass * dt
29+
self.velocity += (self.calculate_pressure_force(self.index) / self.density) * dt
30+
31+
self.resolve_collisions(screen)
32+
33+
self.pos.x += self.velocity.x * dt
34+
self.pos.y += self.velocity.y * dt
35+
self.set_color()
36+
37+
38+
def set_color(self):
39+
v = self.velocity.magnitude()
40+
v = min(max(v, 0.0), self.max_speed)
41+
42+
t = v / self.max_speed # normalize to [0, 1]
43+
44+
# Define color anchors
45+
c0 = (23, 157, 170) # 0 m/s
46+
c1 = (198, 241, 83) # mid
47+
c2 = (252, 70, 4) # max
48+
49+
if t <= 0.5:
50+
# interpolate between c0 and c1
51+
u = t / 0.5
52+
r = int(c0[0] + (c1[0] - c0[0]) * u)
53+
g = int(c0[1] + (c1[1] - c0[1]) * u)
54+
b = int(c0[2] + (c1[2] - c0[2]) * u)
55+
else:
56+
# interpolate between c1 and c2
57+
u = (t - 0.5) / 0.5
58+
r = int(c1[0] + (c2[0] - c1[0]) * u)
59+
g = int(c1[1] + (c2[1] - c1[1]) * u)
60+
b = int(c1[2] + (c2[2] - c1[2]) * u)
61+
62+
self.color = pygame.Color(r, g, b)
63+
64+
65+
@staticmethod
66+
def smoothing_kernal(radius, dst):
67+
if dst >= radius:
68+
return 0
69+
70+
volume = math.pi * radius**4 / 6
71+
value = (radius - dst)**2
72+
return value / volume
73+
74+
@staticmethod
75+
def smoothing_kernal_derivative(radius, dst):
76+
if dst >= radius:
77+
return 0
78+
79+
scale = 12 / (radius**4 * math.pi)
80+
value = dst - radius
81+
return value * scale
82+
83+
84+
def calculate_density(self, samplePoint):
85+
rho = 0
86+
87+
for particle in Particle.particles:
88+
dst = (samplePoint - particle.pos).magnitude()
89+
influence = Particle.smoothing_kernal(Particle.smoothingRadius, dst)
90+
rho += influence * self.mass
91+
92+
self.density = rho
93+
return rho
94+
95+
def update_densities(self):
96+
for i in range(len(Particle.particles)):
97+
Particle.densities[i] = self.calculate_density(Particle.particles[i].pos)
98+
99+
100+
def calculate_pressure_force(self, particle_index):
101+
pressure_gradient = pygame.Vector2(0, 0)
102+
103+
for i in range(len(Particle.particles)):
104+
if i == particle_index:
105+
continue
106+
107+
offset = Particle.particles[i].pos - Particle.particles[particle_index].pos
108+
dst = offset.magnitude()
109+
dir = offset / dst if dst > 0 else pygame.Vector2(1, 0)
110+
slope = Particle.smoothing_kernal_derivative(Particle.smoothingRadius, dst)
111+
density = Particle.densities[i]
112+
shared_pressure = Particle.calculate_shared_pressure(Particle.particles[i].density, Particle.particles[particle_index].density)
113+
pressure_gradient += -shared_pressure * dir * slope * self.mass / density
114+
115+
return pressure_gradient
116+
117+
118+
@staticmethod
119+
def convert_density_to_pressure(density):
120+
targetDensity = 15
121+
pressureMultiplier = 4
122+
123+
densityError = density - targetDensity
124+
pressure = densityError * pressureMultiplier
125+
return pressure
126+
127+
128+
@staticmethod
129+
def calculate_shared_pressure(densityA, densityB):
130+
return (Particle.convert_density_to_pressure(densityA) + Particle.convert_density_to_pressure(densityB)) / 2
131+
132+
133+
def resolve_collisions(self, screen):
134+
collided = False
135+
136+
# Wall Collision Detection
137+
if self.pos.x + self.radius > screen.get_width():
138+
self.velocity.x *= -1
139+
self.pos.x = screen.get_width() - self.radius
140+
collided = True
141+
elif self.pos.x - self.radius < 0:
142+
self.velocity.x *= -1
143+
self.pos.x = self.radius
144+
collided = True
145+
if self.pos.y + self.radius > screen.get_height():
146+
self.velocity.y *= -1
147+
self.pos.y = screen.get_height() - self.radius
148+
collided = True
149+
elif self.pos.y - self.radius < 0:
150+
self.velocity.y *= -1
151+
self.pos.y = self.radius
152+
collided = True
153+
154+
# Kinetic Energy Loss
155+
if collided:
156+
self.velocity /= math.sqrt(2)
157+
158+
if self.velocity.magnitude() < 1E-9:
159+
self.velocity *= 0
160+
161+
162+
163+
164+
def main():
165+
pygame.init()
166+
screen = pygame.display.set_mode((1000, 700))
167+
clock = pygame.time.Clock()
168+
running = True
169+
dt = 1
170+
r = 15
171+
ptcl_color = "WHITE"
172+
bg_color = "BLACK"
173+
num_particles = 40
174+
font = pygame.font.SysFont("Arial", 20)
175+
forces = [pygame.Vector2(0, 9.8*30)]
176+
net_ext_force = pygame.Vector2(0, 0)
177+
178+
for i in range(num_particles):
179+
Particle(pygame.Vector2(random.randint(r, screen.get_width()-r),
180+
random.randint(r, screen.get_height()-r)),
181+
pygame.Vector2(0, 0), r, ptcl_color)
182+
183+
while running:
184+
for event in pygame.event.get():
185+
if event.type == pygame.QUIT:
186+
running = False
187+
if event.type == pygame.KEYDOWN:
188+
if event.key == pygame.K_UP:
189+
forces[0] = pygame.Vector2(0, -9.8*60)
190+
elif event.key == pygame.K_DOWN:
191+
forces[0] = pygame.Vector2(0, 9.8*60)
192+
elif event.key == pygame.K_LEFT:
193+
forces[0] = pygame.Vector2(-9.8*60, 0)
194+
elif event.key == pygame.K_RIGHT:
195+
forces[0] = pygame.Vector2(9.8*60, 0)
196+
197+
net_ext_force = forces[0]
198+
199+
dt = clock.tick(60) / 1000
200+
201+
screen.fill(bg_color)
202+
203+
for particle in Particle.particles:
204+
t1 = threading.Thread(target=particle.update, args=(screen, net_ext_force, dt))
205+
t1.start()
206+
t1.join()
207+
208+
for particle in Particle.particles:
209+
pygame.draw.circle(screen, particle.color, particle.pos, particle.radius)
210+
211+
text_surface = font.render(f"FPS: {(1 / dt):.0f}", True, (255, 255, 255))
212+
screen.blit(text_surface, (10, 10))
213+
214+
pygame.display.flip()
215+
216+
pygame.quit()
217+
218+
219+
if __name__ == "__main__":
220+
main()

0 commit comments

Comments
 (0)