1
+ import pygame
2
+ import moderngl
3
+ import numpy as np
4
+ from pyrr import Matrix44 , Vector3
5
+ from pygame .locals import *
6
+ import threading
7
+ import time
8
+ import pmma
9
+
10
+ pmma .init ()
11
+
12
+
13
+ class ExpandableGrid :
14
+ def __init__ (self , initial_size , height_function ):
15
+ self .size = initial_size
16
+ self .height_function = height_function
17
+ self .vertices = []
18
+ self .indices = []
19
+ self .generate_grid ()
20
+
21
+ def generate_grid (self ):
22
+ """Initializes vertices and indices for the grid."""
23
+ half_size = self .size // 2
24
+ self .vertices = []
25
+
26
+ # Generate vertices
27
+ for z in range (- half_size , half_size + 1 ):
28
+ for x in range (- half_size , half_size + 1 ):
29
+ y = self .height_function (x , z )
30
+ self .vertices .append ((x , y , z ))
31
+
32
+ # Generate indices for triangle-based grid cells
33
+ self .indices = []
34
+ for z in range (self .size ):
35
+ for x in range (self .size ):
36
+ i0 = z * (self .size + 1 ) + x
37
+ i1 = i0 + 1
38
+ i2 = i0 + (self .size + 1 )
39
+ i3 = i2 + 1
40
+ self .indices .extend ([i0 , i2 , i1 , i1 , i2 , i3 ])
41
+
42
+ # Convert to numpy arrays
43
+ self .vertices = np .array (self .vertices , dtype = 'f4' )
44
+ self .indices = np .array (self .indices , dtype = np .uint32 )
45
+
46
+ def expand_grid (self ):
47
+ """Expands the grid by one row and column without regenerating the entire grid."""
48
+ self .size += 1
49
+ half_size = self .size // 2
50
+
51
+ # Add new vertices only on the outer edges
52
+ new_vertices = []
53
+ for z in range (- half_size , half_size + 1 ):
54
+ for x in range (- half_size , half_size + 1 ):
55
+ if abs (x ) == half_size or abs (z ) == half_size :
56
+ y = self .height_function (x , z )
57
+ new_vertices .append ((x , y , z ))
58
+
59
+ self .vertices = np .vstack ((self .vertices , np .array (new_vertices , dtype = 'f4' )))
60
+
61
+ # Add new indices for the expanded grid cells
62
+ new_indices = []
63
+ for z in range (self .size - 1 , self .size ):
64
+ for x in range (self .size - 1 ):
65
+ i0 = z * (self .size + 1 ) + x
66
+ i1 = i0 + 1
67
+ i2 = i0 + (self .size + 1 )
68
+ i3 = i2 + 1
69
+ new_indices .extend ([i0 , i2 , i1 , i1 , i2 , i3 ])
70
+
71
+ for x in range (self .size - 1 , self .size ):
72
+ for z in range (self .size - 1 ):
73
+ i0 = z * (self .size + 1 ) + x
74
+ i1 = i0 + 1
75
+ i2 = i0 + (self .size + 1 )
76
+ i3 = i2 + 1
77
+ new_indices .extend ([i0 , i2 , i1 , i1 , i2 , i3 ])
78
+
79
+ self .indices = np .concatenate ((self .indices , np .array (new_indices , dtype = np .uint32 )))
80
+
81
+ class Renderer :
82
+ def __init__ (self , width , height , mesh ):
83
+ pygame .init ()
84
+ self .width = width
85
+ self .height = height
86
+ self .window = pygame .display .set_mode ((width , height ), OPENGL | DOUBLEBUF , vsync = True )
87
+ self .ctx = moderngl .create_context ()
88
+ self .mesh = mesh
89
+
90
+ self .program = self .ctx .program (
91
+ vertex_shader = """
92
+ #version 330
93
+ in vec3 in_vert;
94
+ uniform mat4 model;
95
+ uniform mat4 view;
96
+ uniform mat4 projection;
97
+ void main() {
98
+ gl_Position = projection * view * model * vec4(in_vert, 1.0);
99
+ }
100
+ """ ,
101
+ fragment_shader = """
102
+ #version 330
103
+ out vec4 fragColor;
104
+ void main() {
105
+ fragColor = vec4(0.3, 0.6, 0.8, 1.0);
106
+ }
107
+ """
108
+ )
109
+
110
+ self .vbo = self .ctx .buffer (self .mesh .vertices .tobytes ())
111
+ self .ibo = self .ctx .buffer (self .mesh .indices .tobytes ())
112
+ self .vao = self .ctx .vertex_array (self .program , [(self .vbo , '3f' , 'in_vert' )], self .ibo )
113
+
114
+ self .camera_pos = Vector3 ([0.0 , 10.0 , 10.0 ])
115
+ self .camera_front = Vector3 ([0.0 , - 1.0 , - 1.0 ]).normalized
116
+ self .camera_up = Vector3 ([0.0 , 1.0 , 0.0 ])
117
+
118
+ self .projection = Matrix44 .perspective_projection (45.0 , width / height , 0.1 , 100.0 )
119
+ self .ctx .viewport = (0 , 0 , width , height )
120
+
121
+ self .t = threading .Thread (target = self .expander )
122
+ self .t .start ()
123
+
124
+ self .update_pending = False
125
+
126
+ def expander (self ):
127
+ while True :
128
+ if self .update_pending is False :
129
+ mesh .expand_grid ()
130
+ mesh .expand_grid ()
131
+ self .update_pending = True
132
+ time .sleep (0.5 )
133
+
134
+ def render (self ):
135
+ self .ctx .clear (0.2 , 0.3 , 0.3 )
136
+
137
+ view = Matrix44 .look_at (
138
+ self .camera_pos ,
139
+ self .camera_pos + self .camera_front ,
140
+ self .camera_up
141
+ )
142
+
143
+ self .program ['model' ].write (Matrix44 .identity ().astype ('f4' ).tobytes ())
144
+ self .program ['view' ].write (view .astype ('f4' ).tobytes ())
145
+ self .program ['projection' ].write (self .projection .astype ('f4' ).tobytes ())
146
+
147
+ self .vao .render (moderngl .TRIANGLES )
148
+
149
+ if self .update_pending :
150
+ self .vbo .release ()
151
+ self .ibo .release ()
152
+ self .vao .release ()
153
+ self .vbo = self .ctx .buffer (self .mesh .vertices .tobytes ())
154
+ self .ibo = self .ctx .buffer (self .mesh .indices .tobytes ())
155
+ self .vao = self .ctx .vertex_array (self .program , [(self .vbo , '3f' , 'in_vert' )], self .ibo )
156
+ self .update_pending = False
157
+
158
+ def process_input (self ):
159
+ keys = pygame .key .get_pressed ()
160
+ camera_speed = 0.05
161
+ if keys [K_w ]:
162
+ self .camera_pos += camera_speed * self .camera_front
163
+ if keys [K_s ]:
164
+ self .camera_pos -= camera_speed * self .camera_front
165
+ if keys [K_a ]:
166
+ self .camera_pos -= np .cross (self .camera_front , self .camera_up ) * camera_speed
167
+ if keys [K_d ]:
168
+ self .camera_pos += np .cross (self .camera_front , self .camera_up ) * camera_speed
169
+
170
+ for event in pygame .event .get ():
171
+ if event .type == QUIT :
172
+ pygame .quit ()
173
+ quit ()
174
+ if event .type == MOUSEBUTTONDOWN :
175
+ if event .button == 4 : # Scroll up
176
+ self .camera_pos += camera_speed * self .camera_front
177
+ elif event .button == 5 : # Scroll down
178
+ self .camera_pos -= camera_speed * self .camera_front
179
+
180
+ if pygame .mouse .get_pressed ()[0 ]: # Left mouse button
181
+ mouse_pos = pygame .mouse .get_pos ()
182
+ if self .last_mouse_pos :
183
+ dx = mouse_pos [0 ] - self .last_mouse_pos [0 ]
184
+ dy = mouse_pos [1 ] - self .last_mouse_pos [1 ]
185
+ sensitivity = 0.01
186
+ dx *= sensitivity
187
+ dy *= sensitivity
188
+
189
+ # Update camera direction (Euler angles)
190
+ self .camera_front [0 ] += dx
191
+ self .camera_front [1 ] -= dy
192
+ self .last_mouse_pos = mouse_pos
193
+ else :
194
+ self .last_mouse_pos = None
195
+
196
+ noise = pmma .Perlin ()
197
+
198
+ # Usage example
199
+ initial_size = 10
200
+ def height_function (x , z ):
201
+ return noise .generate_2D_perlin_noise (x / 100 , z / 100 , new_range = [0 , 1 ])
202
+
203
+ mesh = ExpandableGrid (initial_size , height_function )
204
+ renderer = Renderer (800 , 600 , mesh )
205
+ while True :
206
+ renderer .process_input ()
207
+ renderer .render ()
208
+ pygame .display .flip ()
209
+ pmma .compute ()
0 commit comments