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