e2D 2.0.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- e2D/__init__.py +461 -0
- e2D/commons.py +56 -0
- e2D/cvectors.c +27800 -0
- e2D/cvectors.cp313-win_amd64.pyd +0 -0
- e2D/cvectors.pxd +56 -0
- e2D/cvectors.pyx +561 -0
- e2D/devices.py +74 -0
- e2D/plots.py +584 -0
- e2D/shaders/curve_fragment.glsl +6 -0
- e2D/shaders/curve_vertex.glsl +16 -0
- e2D/shaders/line_instanced_vertex.glsl +37 -0
- e2D/shaders/plot_grid_fragment.glsl +48 -0
- e2D/shaders/plot_grid_vertex.glsl +7 -0
- e2D/shaders/segment_fragment.glsl +6 -0
- e2D/shaders/segment_vertex.glsl +9 -0
- e2D/shaders/stream_fragment.glsl +11 -0
- e2D/shaders/stream_shift_compute.glsl +16 -0
- e2D/shaders/stream_vertex.glsl +27 -0
- e2D/shapes.py +1081 -0
- e2D/text_renderer.py +491 -0
- e2D/vectors.py +247 -0
- e2d-2.0.0.dist-info/METADATA +260 -0
- e2d-2.0.0.dist-info/RECORD +26 -0
- e2d-2.0.0.dist-info/WHEEL +5 -0
- e2d-2.0.0.dist-info/licenses/LICENSE +21 -0
- e2d-2.0.0.dist-info/top_level.txt +1 -0
e2D/__init__.py
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""
|
|
2
|
+
e2D - High-Performance 2D Graphics and Math Library
|
|
3
|
+
Combines ultra-optimized vector operations with moderngl rendering
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2025 Riccardo Mariani
|
|
6
|
+
MIT License
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "2.0.0"
|
|
10
|
+
__author__ = "Riccardo Mariani"
|
|
11
|
+
__email__ = "ricomari2006@gmail.com"
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import moderngl
|
|
15
|
+
import glfw
|
|
16
|
+
import time
|
|
17
|
+
import os
|
|
18
|
+
|
|
19
|
+
# Import original e2D modules
|
|
20
|
+
from .text_renderer import DEFAULT_TEXT_STYLE, Pivots, TextRenderer, TextLabel, TextStyle
|
|
21
|
+
from .shapes import ShapeRenderer, ShapeLabel, InstancedShapeBatch, FillMode
|
|
22
|
+
from .devices import Keyboard, Mouse, KeyState
|
|
23
|
+
from .commons import get_pattr, get_pattr_value, set_pattr_value, get_uniform
|
|
24
|
+
|
|
25
|
+
from typing import Optional
|
|
26
|
+
|
|
27
|
+
# Import optimized Vector2D
|
|
28
|
+
try:
|
|
29
|
+
from .cvectors import (
|
|
30
|
+
Vector2D,
|
|
31
|
+
batch_add_inplace,
|
|
32
|
+
batch_scale_inplace,
|
|
33
|
+
batch_normalize_inplace,
|
|
34
|
+
vectors_to_array,
|
|
35
|
+
array_to_vectors,
|
|
36
|
+
)
|
|
37
|
+
_VECTOR_COMPILED = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
# Fallback to pure Python implementation
|
|
40
|
+
import warnings
|
|
41
|
+
warnings.warn(
|
|
42
|
+
"Vector2D compiled extension not available. "
|
|
43
|
+
"Install Cython and rebuild for optimal performance.",
|
|
44
|
+
RuntimeWarning
|
|
45
|
+
)
|
|
46
|
+
from .vectors import (
|
|
47
|
+
Vector2D,
|
|
48
|
+
batch_add_inplace,
|
|
49
|
+
batch_scale_inplace,
|
|
50
|
+
batch_normalize_inplace,
|
|
51
|
+
vectors_to_array,
|
|
52
|
+
array_to_vectors,
|
|
53
|
+
)
|
|
54
|
+
_VECTOR_COMPILED = False
|
|
55
|
+
|
|
56
|
+
# Import vector utilities
|
|
57
|
+
from .vectors import (
|
|
58
|
+
V2,
|
|
59
|
+
Vec2,
|
|
60
|
+
CommonVectors,
|
|
61
|
+
lerp,
|
|
62
|
+
create_grid,
|
|
63
|
+
create_circle,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DefEnv:
|
|
68
|
+
ctx: moderngl.Context
|
|
69
|
+
def __init__(self) -> None: ...
|
|
70
|
+
|
|
71
|
+
def draw(self) -> None: ...
|
|
72
|
+
|
|
73
|
+
def update(self) -> None: ...
|
|
74
|
+
|
|
75
|
+
def on_resize(self, width: int, height: int) -> None: ...
|
|
76
|
+
|
|
77
|
+
class RootEnv:
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
window_size:tuple[int, int]=(1920, 1080),
|
|
81
|
+
target_fps:int=60,
|
|
82
|
+
vsync:bool=True,
|
|
83
|
+
version:tuple[int, int]=(4,3),
|
|
84
|
+
monitor:Optional[int]=None
|
|
85
|
+
) -> None:
|
|
86
|
+
|
|
87
|
+
if not glfw.init():
|
|
88
|
+
raise RuntimeError("GLFW initialization failed")
|
|
89
|
+
|
|
90
|
+
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, version[0])
|
|
91
|
+
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, version[1])
|
|
92
|
+
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
|
|
93
|
+
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
|
|
94
|
+
glfw.window_hint(glfw.RESIZABLE, True)
|
|
95
|
+
|
|
96
|
+
self.window_size = window_size
|
|
97
|
+
self.target_fps = target_fps
|
|
98
|
+
|
|
99
|
+
self.window : glfw._GLFWwindow = glfw.create_window(window_size[0], window_size[1], "e2D", monitor, None)
|
|
100
|
+
if not self.window:
|
|
101
|
+
glfw.terminate()
|
|
102
|
+
raise RuntimeError("Failed to create GLFW window")
|
|
103
|
+
|
|
104
|
+
glfw.make_context_current(self.window)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
self.ctx = moderngl.create_context()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"Error creating ModernGL context: {e}")
|
|
110
|
+
glfw.terminate()
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
# VSync control - must be set AFTER context creation
|
|
114
|
+
if vsync:
|
|
115
|
+
glfw.swap_interval(1)
|
|
116
|
+
else:
|
|
117
|
+
glfw.swap_interval(0)
|
|
118
|
+
|
|
119
|
+
print(f"OpenGL Context: {self.ctx.version_code} / {self.ctx.info['GL_RENDERER']}")
|
|
120
|
+
|
|
121
|
+
self.ctx.enable(moderngl.BLEND)
|
|
122
|
+
self.ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA
|
|
123
|
+
|
|
124
|
+
# Set initial viewport
|
|
125
|
+
fb_size = glfw.get_framebuffer_size(self.window)
|
|
126
|
+
self.ctx.viewport = (0, 0, fb_size[0], fb_size[1])
|
|
127
|
+
|
|
128
|
+
glfw.set_window_size_callback(self.window, self._on_resize)
|
|
129
|
+
|
|
130
|
+
self.programs :dict[str, moderngl.Program]= {}
|
|
131
|
+
self.compute_shaders :dict[str, moderngl.ComputeShader]= {}
|
|
132
|
+
self.buffers :dict[str, moderngl.Buffer]= {}
|
|
133
|
+
|
|
134
|
+
self.keyboard = Keyboard()
|
|
135
|
+
self.mouse = Mouse()
|
|
136
|
+
self.text_renderer = TextRenderer(self.ctx)
|
|
137
|
+
self.shape_renderer = ShapeRenderer(self.ctx)
|
|
138
|
+
|
|
139
|
+
# Delta time tracking
|
|
140
|
+
self.delta = 0.0
|
|
141
|
+
self.last_frame_time = time.time()
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def window_size_f(self) -> tuple[float, float]:
|
|
145
|
+
"""Get window size as floats for shader uniforms."""
|
|
146
|
+
return (float(self.window_size[0]), float(self.window_size[1]))
|
|
147
|
+
|
|
148
|
+
def _on_resize(self, window, width, height) -> None:
|
|
149
|
+
self.window_size = (width, height)
|
|
150
|
+
fb_size = glfw.get_framebuffer_size(window)
|
|
151
|
+
self.ctx.viewport = (0, 0, fb_size[0], fb_size[1])
|
|
152
|
+
if hasattr(self, 'env') and hasattr(self.env, 'on_resize'):
|
|
153
|
+
self.env.on_resize(fb_size[0], fb_size[1])
|
|
154
|
+
|
|
155
|
+
def init(self, env:DefEnv) -> "RootEnv":
|
|
156
|
+
self.env = env
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def load_shader_file(self, path:str) -> str:
|
|
160
|
+
"""Load shader source code from a file.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
path: Path to shader file (relative to working directory or absolute)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Shader source code as string
|
|
167
|
+
"""
|
|
168
|
+
if not os.path.exists(path):
|
|
169
|
+
raise FileNotFoundError(f"Shader file not found: {path}")
|
|
170
|
+
|
|
171
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
172
|
+
return f.read()
|
|
173
|
+
|
|
174
|
+
def create_program(self, vertex_shader:str, fragment_shader:str, id:str) -> moderngl.Program:
|
|
175
|
+
new_program = self.ctx.program(
|
|
176
|
+
vertex_shader=vertex_shader,
|
|
177
|
+
fragment_shader=fragment_shader
|
|
178
|
+
)
|
|
179
|
+
if id in self.programs:
|
|
180
|
+
print(f"Warning: Program with id '{id}' already exists. Overwriting.")
|
|
181
|
+
self.programs[id] = new_program
|
|
182
|
+
return new_program
|
|
183
|
+
|
|
184
|
+
def create_program_from_files(self, vertex_path:str, fragment_path:str, id:str) -> moderngl.Program:
|
|
185
|
+
"""Create a shader program from shader files.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
vertex_path: Path to vertex shader file
|
|
189
|
+
fragment_path: Path to fragment shader file
|
|
190
|
+
id: Identifier for the program
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
The created program
|
|
194
|
+
"""
|
|
195
|
+
vertex_shader = self.load_shader_file(vertex_path)
|
|
196
|
+
fragment_shader = self.load_shader_file(fragment_path)
|
|
197
|
+
return self.create_program(vertex_shader, fragment_shader, id)
|
|
198
|
+
|
|
199
|
+
def get_program(self, id:str) -> moderngl.Program | None:
|
|
200
|
+
return self.programs.get(id, None)
|
|
201
|
+
|
|
202
|
+
def __draw__(self) -> None:
|
|
203
|
+
self.ctx.clear(0.0, 0.0, 0.0, 1.0)
|
|
204
|
+
self.env.draw()
|
|
205
|
+
|
|
206
|
+
def create_compute_shader(self, compute_shader:str, id:str) -> moderngl.ComputeShader:
|
|
207
|
+
"""Create and store a compute shader program."""
|
|
208
|
+
new_compute = self.ctx.compute_shader(compute_shader)
|
|
209
|
+
if id in self.compute_shaders:
|
|
210
|
+
print(f"Warning: Compute shader with id '{id}' already exists. Overwriting.")
|
|
211
|
+
self.compute_shaders[id] = new_compute
|
|
212
|
+
return new_compute
|
|
213
|
+
|
|
214
|
+
def create_compute_shader_from_file(self, compute_path:str, id:str) -> moderngl.ComputeShader:
|
|
215
|
+
"""Create a compute shader from a file.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
compute_path: Path to compute shader file
|
|
219
|
+
id: Identifier for the compute shader
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
The created compute shader
|
|
223
|
+
"""
|
|
224
|
+
compute_shader = self.load_shader_file(compute_path)
|
|
225
|
+
return self.create_compute_shader(compute_shader, id)
|
|
226
|
+
|
|
227
|
+
def get_compute_shader(self, id:str) -> moderngl.ComputeShader | None:
|
|
228
|
+
"""Retrieve a compute shader by id."""
|
|
229
|
+
return self.compute_shaders.get(id, None)
|
|
230
|
+
|
|
231
|
+
def create_buffer(self, data=None, reserve:int=0, id:Optional[str]=None, dynamic:bool=True) -> moderngl.Buffer:
|
|
232
|
+
"""Create and optionally store a buffer.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
data: Initial data (numpy array, bytes, or None)
|
|
236
|
+
reserve: Reserve size in bytes if data is None
|
|
237
|
+
id: Optional identifier to store the buffer
|
|
238
|
+
dynamic: If True, buffer is marked for frequent updates
|
|
239
|
+
"""
|
|
240
|
+
if data is not None:
|
|
241
|
+
if isinstance(data, np.ndarray):
|
|
242
|
+
buffer = self.ctx.buffer(data.tobytes(), dynamic=dynamic)
|
|
243
|
+
else:
|
|
244
|
+
buffer = self.ctx.buffer(data, dynamic=dynamic)
|
|
245
|
+
else:
|
|
246
|
+
buffer = self.ctx.buffer(reserve=reserve, dynamic=dynamic)
|
|
247
|
+
|
|
248
|
+
if id is not None:
|
|
249
|
+
if id in self.buffers:
|
|
250
|
+
print(f"Warning: Buffer with id '{id}' already exists. Overwriting.")
|
|
251
|
+
self.buffers[id] = buffer
|
|
252
|
+
|
|
253
|
+
return buffer
|
|
254
|
+
|
|
255
|
+
def get_buffer(self, id:str) -> moderngl.Buffer | None:
|
|
256
|
+
"""Retrieve a buffer by id."""
|
|
257
|
+
return self.buffers.get(id, None)
|
|
258
|
+
|
|
259
|
+
def bind_buffer_to_storage(self, buffer:moderngl.Buffer|str, binding:int) -> None:
|
|
260
|
+
"""Bind a buffer to a shader storage binding point.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
buffer: Buffer object or buffer id
|
|
264
|
+
binding: SSBO binding point (0, 1, 2, ...)
|
|
265
|
+
"""
|
|
266
|
+
if isinstance(buffer, str):
|
|
267
|
+
loc_buffer = self.get_buffer(buffer)
|
|
268
|
+
if loc_buffer is None:
|
|
269
|
+
raise ValueError(f"Buffer with id '{buffer}' does not exist.")
|
|
270
|
+
buffer = loc_buffer
|
|
271
|
+
|
|
272
|
+
buffer.bind_to_storage_buffer(binding)
|
|
273
|
+
|
|
274
|
+
def dispatch_compute(self,
|
|
275
|
+
compute_id:str|moderngl.ComputeShader,
|
|
276
|
+
groups_x:int=1, groups_y:int=1, groups_z:int=1,
|
|
277
|
+
buffers:Optional[dict[int, moderngl.Buffer|str]]=None,
|
|
278
|
+
wait:bool=True
|
|
279
|
+
) -> None:
|
|
280
|
+
"""Dispatch a compute shader with automatic buffer binding.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
compute_id: Compute shader object or id
|
|
284
|
+
groups_x, groups_y, groups_z: Number of work groups
|
|
285
|
+
buffers: Optional dict mapping binding points to buffers {0: buffer, 1: 'buffer_id', ...}
|
|
286
|
+
wait: If True, wait for compute to complete before returning
|
|
287
|
+
"""
|
|
288
|
+
if isinstance(compute_id, str):
|
|
289
|
+
compute = self.get_compute_shader(compute_id)
|
|
290
|
+
if compute is None:
|
|
291
|
+
raise ValueError(f"Compute shader with id '{compute_id}' does not exist.")
|
|
292
|
+
else:
|
|
293
|
+
compute = compute_id
|
|
294
|
+
|
|
295
|
+
# Bind buffers if provided
|
|
296
|
+
if buffers:
|
|
297
|
+
for binding, buffer in buffers.items():
|
|
298
|
+
self.bind_buffer_to_storage(buffer, binding)
|
|
299
|
+
|
|
300
|
+
# Run compute shader
|
|
301
|
+
compute.run(groups_x, groups_y, groups_z)
|
|
302
|
+
|
|
303
|
+
# Memory barrier to ensure compute writes are visible
|
|
304
|
+
if wait:
|
|
305
|
+
self.ctx.memory_barrier()
|
|
306
|
+
|
|
307
|
+
def read_buffer(self, buffer:moderngl.Buffer|str, dtype='f4') -> np.ndarray:
|
|
308
|
+
"""Read data from a buffer into a numpy array.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
buffer: Buffer object or buffer id
|
|
312
|
+
dtype: numpy dtype for the output array
|
|
313
|
+
"""
|
|
314
|
+
if isinstance(buffer, str):
|
|
315
|
+
loc_buffer = self.get_buffer(buffer)
|
|
316
|
+
if loc_buffer is None:
|
|
317
|
+
raise ValueError(f"Buffer with id '{buffer}' does not exist.")
|
|
318
|
+
buffer = loc_buffer
|
|
319
|
+
|
|
320
|
+
data = np.frombuffer(buffer.read(), dtype=dtype)
|
|
321
|
+
return data
|
|
322
|
+
|
|
323
|
+
def write_buffer(self, buffer:moderngl.Buffer|str, data, offset:int=0) -> None:
|
|
324
|
+
"""Write data to a buffer.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
buffer: Buffer object or buffer id
|
|
328
|
+
data: Data to write (numpy array or bytes)
|
|
329
|
+
offset: Byte offset in the buffer
|
|
330
|
+
"""
|
|
331
|
+
if isinstance(buffer, str):
|
|
332
|
+
loc_buffer = self.get_buffer(buffer)
|
|
333
|
+
if loc_buffer is None:
|
|
334
|
+
raise ValueError(f"Buffer with id '{buffer}' does not exist.")
|
|
335
|
+
buffer = loc_buffer
|
|
336
|
+
|
|
337
|
+
if isinstance(data, np.ndarray):
|
|
338
|
+
data = data.tobytes()
|
|
339
|
+
|
|
340
|
+
buffer.write(data, offset=offset)
|
|
341
|
+
|
|
342
|
+
def __update__(self) -> None:
|
|
343
|
+
self.env.update()
|
|
344
|
+
|
|
345
|
+
def get_pattr(self, prog_id:str|moderngl.Program, name:str) -> moderngl.Uniform | moderngl.UniformBlock | moderngl.Attribute | moderngl.Varying:
|
|
346
|
+
return get_pattr(prog_id, name, programs=self.programs)
|
|
347
|
+
|
|
348
|
+
def get_uniform(self, prog_id:str|moderngl.Program|moderngl.ComputeShader, name:str) -> moderngl.Uniform:
|
|
349
|
+
return get_uniform(prog_id, name, compute_shaders=self.compute_shaders, programs=self.programs)
|
|
350
|
+
|
|
351
|
+
def get_pattr_value(self, prog_id:str|moderngl.Program, name:str) -> int|float|tuple|list:
|
|
352
|
+
return get_pattr_value(prog_id, name, programs=self.programs)
|
|
353
|
+
|
|
354
|
+
def set_pattr_value(self, prog_id:str|moderngl.Program, name: str, value, *, force_write: bool= False) -> None:
|
|
355
|
+
return set_pattr_value(prog_id, name, value, force_write=force_write, programs=self.programs)
|
|
356
|
+
|
|
357
|
+
def loop(self) -> None:
|
|
358
|
+
# Register callbacks
|
|
359
|
+
glfw.set_scroll_callback(self.window, self.mouse._on_scroll)
|
|
360
|
+
glfw.set_cursor_pos_callback(self.window, self.mouse._on_cursor_pos)
|
|
361
|
+
glfw.set_mouse_button_callback(self.window, self.mouse._on_mouse_button)
|
|
362
|
+
glfw.set_key_callback(self.window, self.keyboard._on_key)
|
|
363
|
+
|
|
364
|
+
target_frame_time = 1.0 / self.target_fps if (self.target_fps and self.target_fps > 0) else 0.0
|
|
365
|
+
|
|
366
|
+
while not glfw.window_should_close(self.window):
|
|
367
|
+
start_time = time.time()
|
|
368
|
+
|
|
369
|
+
# Calculate delta time
|
|
370
|
+
self.delta = start_time - self.last_frame_time
|
|
371
|
+
self.last_frame_time = start_time
|
|
372
|
+
|
|
373
|
+
self.keyboard.update()
|
|
374
|
+
self.mouse.update()
|
|
375
|
+
glfw.poll_events()
|
|
376
|
+
|
|
377
|
+
if self.keyboard.get_key(glfw.KEY_X, KeyState.JUST_PRESSED):
|
|
378
|
+
glfw.set_window_should_close(self.window, True)
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
self.__update__()
|
|
382
|
+
self.__draw__()
|
|
383
|
+
glfw.swap_buffers(self.window)
|
|
384
|
+
except Exception as e:
|
|
385
|
+
print(f"Error in loop: {e}")
|
|
386
|
+
import traceback
|
|
387
|
+
traceback.print_exc()
|
|
388
|
+
break
|
|
389
|
+
|
|
390
|
+
# FPS Limiting
|
|
391
|
+
if target_frame_time > 0:
|
|
392
|
+
elapsed = time.time() - start_time
|
|
393
|
+
wait = target_frame_time - elapsed
|
|
394
|
+
if wait > 0:
|
|
395
|
+
time.sleep(wait)
|
|
396
|
+
|
|
397
|
+
glfw.terminate()
|
|
398
|
+
|
|
399
|
+
def print(
|
|
400
|
+
self,
|
|
401
|
+
text_or_label: str|TextLabel,
|
|
402
|
+
position: tuple[float, float],
|
|
403
|
+
scale: float = 1.0,
|
|
404
|
+
style: TextStyle = DEFAULT_TEXT_STYLE,
|
|
405
|
+
pivot: Pivots = Pivots.TOP_LEFT,
|
|
406
|
+
save_cache: bool = False
|
|
407
|
+
) -> Optional[TextLabel]:
|
|
408
|
+
|
|
409
|
+
if isinstance(text_or_label, TextLabel):
|
|
410
|
+
text_or_label.draw()
|
|
411
|
+
else:
|
|
412
|
+
if save_cache:
|
|
413
|
+
return self.text_renderer.create_label(str(text_or_label), position[0], position[1], scale, style, pivot)
|
|
414
|
+
else:
|
|
415
|
+
self.text_renderer.draw_text(str(text_or_label), position, scale, style, pivot)
|
|
416
|
+
|
|
417
|
+
# ========== Shape Drawing Methods ==========
|
|
418
|
+
|
|
419
|
+
def draw_circle(self, center: tuple[float, float], radius: float, **kwargs) -> None:
|
|
420
|
+
"""Draw a circle. See ShapeRenderer.draw_circle for parameters."""
|
|
421
|
+
self.shape_renderer.draw_circle(center, radius, **kwargs)
|
|
422
|
+
|
|
423
|
+
def draw_rect(self, position: tuple[float, float], size: tuple[float, float], **kwargs) -> None:
|
|
424
|
+
"""Draw a rectangle. See ShapeRenderer.draw_rect for parameters."""
|
|
425
|
+
self.shape_renderer.draw_rect(position, size, **kwargs)
|
|
426
|
+
|
|
427
|
+
def draw_line(self, start: tuple[float, float], end: tuple[float, float], **kwargs) -> None:
|
|
428
|
+
"""Draw a line. See ShapeRenderer.draw_line for parameters."""
|
|
429
|
+
self.shape_renderer.draw_line(start, end, **kwargs)
|
|
430
|
+
|
|
431
|
+
def draw_lines(self, points, **kwargs) -> None:
|
|
432
|
+
"""Draw a polyline. See ShapeRenderer.draw_lines for parameters."""
|
|
433
|
+
self.shape_renderer.draw_lines(points, **kwargs)
|
|
434
|
+
|
|
435
|
+
def create_circle(self, center: tuple[float, float], radius: float, **kwargs) -> ShapeLabel:
|
|
436
|
+
"""Create a cached circle. See ShapeRenderer.create_circle for parameters."""
|
|
437
|
+
return self.shape_renderer.create_circle(center, radius, **kwargs)
|
|
438
|
+
|
|
439
|
+
def create_rect(self, position: tuple[float, float], size: tuple[float, float], **kwargs) -> ShapeLabel:
|
|
440
|
+
"""Create a cached rectangle. See ShapeRenderer.create_rect for parameters."""
|
|
441
|
+
return self.shape_renderer.create_rect(position, size, **kwargs)
|
|
442
|
+
|
|
443
|
+
def create_line(self, start: tuple[float, float], end: tuple[float, float], **kwargs) -> ShapeLabel:
|
|
444
|
+
"""Create a cached line. See ShapeRenderer.create_line for parameters."""
|
|
445
|
+
return self.shape_renderer.create_line(start, end, **kwargs)
|
|
446
|
+
|
|
447
|
+
def create_lines(self, points, **kwargs) -> ShapeLabel:
|
|
448
|
+
"""Create a cached polyline. See ShapeRenderer.create_lines for parameters."""
|
|
449
|
+
return self.shape_renderer.create_lines(points, **kwargs)
|
|
450
|
+
|
|
451
|
+
def create_circle_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
452
|
+
"""Create a batch for drawing multiple circles using GPU instancing."""
|
|
453
|
+
return self.shape_renderer.create_circle_batch(max_shapes)
|
|
454
|
+
|
|
455
|
+
def create_rect_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
456
|
+
"""Create a batch for drawing multiple rectangles using GPU instancing."""
|
|
457
|
+
return self.shape_renderer.create_rect_batch(max_shapes)
|
|
458
|
+
|
|
459
|
+
def create_line_batch(self, max_shapes: int = 10000) -> InstancedShapeBatch:
|
|
460
|
+
"""Create a batch for drawing multiple lines using GPU instancing."""
|
|
461
|
+
return self.shape_renderer.create_line_batch(max_shapes)
|
e2D/commons.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import moderngl
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def _is_array_uniform(u: moderngl.Uniform) -> bool:
|
|
5
|
+
return u.array_length > 1
|
|
6
|
+
|
|
7
|
+
def get_pattr(prog_id:str|moderngl.Program, name:str, *, programs: dict[str, moderngl.Program]= {}) -> moderngl.Uniform | moderngl.UniformBlock | moderngl.Attribute | moderngl.Varying:
|
|
8
|
+
if isinstance(prog_id, moderngl.Program):
|
|
9
|
+
return prog_id[name]
|
|
10
|
+
if prog_id not in programs:
|
|
11
|
+
raise ValueError(f"Program with id '{prog_id}' does not exist.")
|
|
12
|
+
return programs[prog_id][name]
|
|
13
|
+
|
|
14
|
+
def get_uniform(prog_id:str|moderngl.Program|moderngl.ComputeShader, name:str, *, compute_shaders: dict[str, moderngl.ComputeShader]= {}, programs: dict[str, moderngl.Program]= {}) -> moderngl.Uniform:
|
|
15
|
+
"""Get a uniform from a program or compute shader with proper typing.
|
|
16
|
+
|
|
17
|
+
This is a type-safe alternative to program[name] which returns a union type.
|
|
18
|
+
Raises TypeError if the attribute is not a Uniform.
|
|
19
|
+
"""
|
|
20
|
+
if isinstance(prog_id, str):
|
|
21
|
+
if prog_id in programs:
|
|
22
|
+
prog_id = programs[prog_id]
|
|
23
|
+
elif prog_id in compute_shaders:
|
|
24
|
+
prog_id = compute_shaders[prog_id]
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(f"Program or compute shader with id '{prog_id}' does not exist.")
|
|
27
|
+
|
|
28
|
+
attr = prog_id[name]
|
|
29
|
+
if not isinstance(attr, moderngl.Uniform):
|
|
30
|
+
raise TypeError(f"'{name}' is {type(attr).__name__}, not a Uniform")
|
|
31
|
+
return attr
|
|
32
|
+
|
|
33
|
+
def get_pattr_value(prog_id:str|moderngl.Program, name:str, *, programs: dict[str, moderngl.Program]= {}) -> int|float|tuple|list:
|
|
34
|
+
attr = get_pattr(prog_id, name, programs=programs)
|
|
35
|
+
if not isinstance(attr, moderngl.Uniform):
|
|
36
|
+
raise TypeError(f"'{name}' is {type(attr).__name__}, not a Uniform")
|
|
37
|
+
if _is_array_uniform(attr):
|
|
38
|
+
raise TypeError(f"Uniform '{name}' is an array; cannot use .value")
|
|
39
|
+
return attr.value
|
|
40
|
+
|
|
41
|
+
def set_pattr_value(prog_id:str|moderngl.Program, name: str, value, *, programs: dict[str, moderngl.Program]= {}, force_write: bool= False) -> None:
|
|
42
|
+
attr = get_pattr(prog_id, name, programs=programs)
|
|
43
|
+
|
|
44
|
+
if not isinstance(attr, moderngl.Uniform):
|
|
45
|
+
raise TypeError(f"'{name}' is {type(attr).__name__}, not a Uniform")
|
|
46
|
+
|
|
47
|
+
use_write = force_write or _is_array_uniform(attr)
|
|
48
|
+
if use_write:
|
|
49
|
+
if isinstance(value, np.ndarray):
|
|
50
|
+
data = value
|
|
51
|
+
else:
|
|
52
|
+
data = np.array(value, dtype="f4")
|
|
53
|
+
|
|
54
|
+
attr.write(data.tobytes())
|
|
55
|
+
else:
|
|
56
|
+
attr.value = value
|