|
| 1 | +import pygame |
| 2 | +import numpy as np |
| 3 | +import moderngl as mgl |
| 4 | +from pyrr import Matrix44 |
| 5 | +import pmma |
| 6 | + |
| 7 | +pmma.init() |
| 8 | + |
| 9 | +# Initialize Pygame |
| 10 | +pygame.init() |
| 11 | +screen = pygame.display.set_mode((1920, 1080), pygame.OPENGL | pygame.DOUBLEBUF) |
| 12 | +clock = pygame.time.Clock() |
| 13 | + |
| 14 | +# Initialize ModernGL context |
| 15 | +ctx = mgl.create_context() |
| 16 | + |
| 17 | +# Vertex Shader |
| 18 | +vertex_shader = """ |
| 19 | +#version 330 |
| 20 | +uniform mat4 projection; |
| 21 | +uniform mat4 view; |
| 22 | +uniform mat4 model; |
| 23 | +
|
| 24 | +in vec3 in_position; // Base sphere vertex |
| 25 | +in vec3 instance_position; // Small sphere instance position |
| 26 | +in vec3 instance_color; // Small sphere instance color |
| 27 | +in float instance_radius; // Small sphere instance radius |
| 28 | +
|
| 29 | +out vec3 frag_color; |
| 30 | +
|
| 31 | +void main() { |
| 32 | + vec3 scaled_position = in_position * instance_radius; // Scale small sphere |
| 33 | + vec3 world_position = scaled_position + instance_position; // Translate to instance position |
| 34 | + gl_Position = projection * view * model * vec4(world_position, 1.0); |
| 35 | + frag_color = instance_color; |
| 36 | +} |
| 37 | +""" |
| 38 | + |
| 39 | +# Fragment Shader |
| 40 | +fragment_shader = """ |
| 41 | +#version 330 |
| 42 | +in vec3 frag_color; |
| 43 | +out vec4 color; |
| 44 | +
|
| 45 | +void main() { |
| 46 | + color = vec4(frag_color, 1.0); |
| 47 | +} |
| 48 | +""" |
| 49 | + |
| 50 | +# Compile shaders and link program |
| 51 | +program = ctx.program( |
| 52 | + vertex_shader=vertex_shader, |
| 53 | + fragment_shader=fragment_shader, |
| 54 | +) |
| 55 | + |
| 56 | +# Uniforms |
| 57 | +projection = program['projection'] |
| 58 | +view = program['view'] |
| 59 | +model = program['model'] |
| 60 | + |
| 61 | +# Generate base sphere geometry (triangular mesh for the small spheres) |
| 62 | +def generate_sphere_mesh(radius, segments): |
| 63 | + vertices = [] |
| 64 | + indices = [] |
| 65 | + for i in range(segments): |
| 66 | + theta1 = np.pi * i / segments |
| 67 | + theta2 = np.pi * (i + 1) / segments |
| 68 | + |
| 69 | + for j in range(segments): |
| 70 | + phi1 = 2 * np.pi * j / segments |
| 71 | + phi2 = 2 * np.pi * (j + 1) / segments |
| 72 | + |
| 73 | + # Sphere vertices |
| 74 | + x1, y1, z1 = radius * np.sin(theta1) * np.cos(phi1), radius * np.sin(theta1) * np.sin(phi1), radius * np.cos(theta1) |
| 75 | + x2, y2, z2 = radius * np.sin(theta2) * np.cos(phi1), radius * np.sin(theta2) * np.sin(phi1), radius * np.cos(theta2) |
| 76 | + x3, y3, z3 = radius * np.sin(theta2) * np.cos(phi2), radius * np.sin(theta2) * np.sin(phi2), radius * np.cos(theta2) |
| 77 | + x4, y4, z4 = radius * np.sin(theta1) * np.cos(phi2), radius * np.sin(theta1) * np.sin(phi2), radius * np.cos(theta1) |
| 78 | + |
| 79 | + # Append vertices |
| 80 | + vertices.extend([(x1, y1, z1), (x2, y2, z2), (x3, y3, z3), (x4, y4, z4)]) |
| 81 | + |
| 82 | + # Add triangle indices |
| 83 | + base = len(vertices) - 4 |
| 84 | + indices.extend([base, base + 1, base + 2, base, base + 2, base + 3]) |
| 85 | + |
| 86 | + return np.array(vertices, dtype='f4'), np.array(indices, dtype='i4') |
| 87 | + |
| 88 | +sphere_vertices, sphere_indices = generate_sphere_mesh(1.0, 16) |
| 89 | + |
| 90 | +# Create buffers for sphere geometry |
| 91 | +vbo_sphere = ctx.buffer(sphere_vertices.tobytes()) |
| 92 | +ibo_sphere = ctx.buffer(sphere_indices.tobytes()) |
| 93 | + |
| 94 | +# Distribute points on a sphere (outer sphere) |
| 95 | +def fibonacci_sphere(samples=100, radius=5.0): |
| 96 | + points = [] |
| 97 | + phi = (1 + np.sqrt(5)) / 2 # Golden ratio |
| 98 | + for i in range(samples): |
| 99 | + z = 1 - (i / (samples - 1)) * 2 # Map z to [-1, 1] |
| 100 | + r = np.sqrt(1 - z * z) # Radius of circle at z |
| 101 | + theta = 2 * np.pi * i / phi # Angle based on golden ratio |
| 102 | + x = np.cos(theta) * r |
| 103 | + y = np.sin(theta) * r |
| 104 | + points.append((x * radius, y * radius, z * radius)) |
| 105 | + return np.array(points, dtype='f4') |
| 106 | + |
| 107 | +# Generate instance data |
| 108 | +instance_positions = fibonacci_sphere(samples=1000, radius=5.0) |
| 109 | +instance_colors = np.random.uniform(0.0, 1.0, (len(instance_positions), 3)).astype('f4') |
| 110 | +instance_radii = np.random.uniform(0.1, 0.1, (len(instance_positions),)).astype('f4') |
| 111 | + |
| 112 | +vbo_instance_position = ctx.buffer(instance_positions.tobytes()) |
| 113 | +vbo_instance_color = ctx.buffer(instance_colors.tobytes()) |
| 114 | +vbo_instance_radius = ctx.buffer(instance_radii.tobytes()) |
| 115 | + |
| 116 | +# Create VAO |
| 117 | +vao = ctx.vertex_array( |
| 118 | + program, |
| 119 | + [ |
| 120 | + (vbo_sphere, '3f', 'in_position'), |
| 121 | + (vbo_instance_position, '3f/i', 'instance_position'), |
| 122 | + (vbo_instance_color, '3f/i', 'instance_color'), |
| 123 | + (vbo_instance_radius, '1f/i', 'instance_radius'), |
| 124 | + ], |
| 125 | + index_buffer=ibo_sphere, |
| 126 | +) |
| 127 | + |
| 128 | +# Projection and view matrices |
| 129 | +projection_matrix = Matrix44.perspective_projection(45.0, 1920 / 1080, 0.1, 100.0) |
| 130 | +view_matrix = Matrix44.look_at( |
| 131 | + eye=(10.0, 10.0, 10.0), |
| 132 | + target=(0.0, 0.0, 0.0), |
| 133 | + up=(0.0, 0.0, 1.0) |
| 134 | +) |
| 135 | +model_matrix = Matrix44.identity() |
| 136 | + |
| 137 | +# Main loop |
| 138 | +running = True |
| 139 | +angle = 0.0 |
| 140 | +x_noise = pmma.Perlin() |
| 141 | +y_noise = pmma.Perlin() |
| 142 | +z_noise = pmma.Perlin() |
| 143 | +while running: |
| 144 | + for event in pygame.event.get(): |
| 145 | + if event.type == pygame.QUIT: |
| 146 | + running = False |
| 147 | + |
| 148 | + # Clear screen |
| 149 | + ctx.clear(0, 0, 0) |
| 150 | + ctx.enable(mgl.DEPTH_TEST) |
| 151 | + |
| 152 | + # Rotate model matrix |
| 153 | + angle += 0.01 |
| 154 | + model_matrix = Matrix44.from_eulers((angle, angle / 2, angle / 3)) |
| 155 | + |
| 156 | + for i, position in enumerate(instance_positions): |
| 157 | + # Use the starting position and time as inputs to the noise function |
| 158 | + r = x_noise.generate_2D_perlin_noise(position[0]/25 + pmma.get_application_run_time()/5, position[1]/25 + pmma.get_application_run_time()/5, new_range=[0, 1]) |
| 159 | + g = y_noise.generate_2D_perlin_noise(position[1]/25 + pmma.get_application_run_time()/5, position[2]/25 + pmma.get_application_run_time()/5, new_range=[0, 1]) |
| 160 | + b = z_noise.generate_2D_perlin_noise(position[2]/25 + pmma.get_application_run_time()/5, position[0]/25 + pmma.get_application_run_time()/5, new_range=[0, 1]) |
| 161 | + |
| 162 | + instance_colors[i] = (r, g, b) |
| 163 | + |
| 164 | + # Update the color buffer |
| 165 | + vbo_instance_color.write(instance_colors.tobytes()) |
| 166 | + |
| 167 | + # Update uniforms |
| 168 | + projection.write(projection_matrix.astype('f4').tobytes()) |
| 169 | + view.write(view_matrix.astype('f4').tobytes()) |
| 170 | + model.write(model_matrix.astype('f4').tobytes()) |
| 171 | + |
| 172 | + # Render spheres |
| 173 | + vao.render(instances=len(instance_positions)) |
| 174 | + |
| 175 | + # Swap buffers |
| 176 | + pygame.display.flip() |
| 177 | + clock.tick(60) |
| 178 | + |
| 179 | +pygame.quit() |
0 commit comments