1"""
2Example showing how to create particle explosions via the GPU.
3"""
4import random
5import time
6import math
7from array import array
8from dataclasses import dataclass
9
10import arcade
11import arcade.gl
12
13SCREEN_WIDTH = 1024
14SCREEN_HEIGHT = 768
15SCREEN_TITLE = "GPU Particle Explosion"
16
17PARTICLE_COUNT = 300
18
19MIN_FADE_TIME = 0.25
20MAX_FADE_TIME = 1.5
21
22
23@dataclass
24class Burst:
25 """ Track for each burst. """
26 buffer: arcade.gl.Buffer
27 vao: arcade.gl.Geometry
28 start_time: float
29
30
31class MyWindow(arcade.Window):
32 """ Main window"""
33 def __init__(self):
34 super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
35 self.burst_list = []
36
37 # Program to visualize the points
38 self.program = self.ctx.load_program(
39 vertex_shader="vertex_shader_v4.glsl",
40 fragment_shader="fragment_shader.glsl",
41 )
42
43 self.ctx.enable_only(self.ctx.BLEND)
44
45 def on_draw(self):
46 """ Draw everything """
47 self.clear()
48
49 # Set the particle size
50 self.ctx.point_size = 2 * self.get_pixel_ratio()
51
52 # Loop through each burst
53 for burst in self.burst_list:
54
55 # Set the uniform data
56 self.program['time'] = time.time() - burst.start_time
57
58 # Render the burst
59 burst.vao.render(self.program, mode=self.ctx.POINTS)
60
61 def on_update(self, dt):
62 """ Update game """
63
64 # Create a copy of our list, as we can't modify a list while iterating
65 # it. Then see if any of the items have completely faded out and need
66 # to be removed.
67 temp_list = self.burst_list.copy()
68 for burst in temp_list:
69 if time.time() - burst.start_time > MAX_FADE_TIME:
70 self.burst_list.remove(burst)
71
72 def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
73 """ User clicks mouse """
74
75 def _gen_initial_data(initial_x, initial_y):
76 """ Generate data for each particle """
77 for i in range(PARTICLE_COUNT):
78 angle = random.uniform(0, 2 * math.pi)
79 speed = abs(random.gauss(0, 1)) * .5
80 dx = math.sin(angle) * speed
81 dy = math.cos(angle) * speed
82 red = random.uniform(0.5, 1.0)
83 green = random.uniform(0, red)
84 blue = 0
85 fade_rate = random.uniform(
86 1 / MAX_FADE_TIME, 1 / MIN_FADE_TIME)
87
88 yield initial_x
89 yield initial_y
90 yield dx
91 yield dy
92 yield red
93 yield green
94 yield blue
95 yield fade_rate
96
97 # Recalculate the coordinates from pixels to the OpenGL system with
98 # 0, 0 at the center.
99 x2 = x / self.width * 2. - 1.
100 y2 = y / self.height * 2. - 1.
101
102 # Get initial particle data
103 initial_data = _gen_initial_data(x2, y2)
104
105 # Create a buffer with that data
106 buffer = self.ctx.buffer(data=array('f', initial_data))
107
108 # Create a buffer description specifying the buffer's data format
109 buffer_description = arcade.gl.BufferDescription(
110 buffer,
111 '2f 2f 3f f',
112 ['in_pos', 'in_vel', 'in_color', 'in_fade_rate'])
113
114 # Create our Vertex Attribute Object
115 vao = self.ctx.geometry([buffer_description])
116
117 # Create the Burst object and add it to the list of bursts
118 burst = Burst(buffer=buffer, vao=vao, start_time=time.time())
119 self.burst_list.append(burst)
120
121
122if __name__ == "__main__":
123 window = MyWindow()
124 window.center_window()
125 arcade.run()