e2D 2.0.0__cp313-cp313-win_amd64.whl → 2.0.2__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 +199 -72
- e2D/__init__.pyi +145 -0
- e2D/ccolors.c +34514 -0
- e2D/ccolors.cp313-win_amd64.pyd +0 -0
- e2D/ccolors.pyi +51 -0
- e2D/ccolors.pyx +350 -0
- e2D/color_defs.py +238 -0
- e2D/colors.py +380 -0
- e2D/colors.pyi +104 -0
- e2D/commons.py +38 -10
- e2D/commons.pyi +79 -0
- e2D/cvectors.c +152 -152
- e2D/cvectors.cp313-win_amd64.pyd +0 -0
- e2D/cvectors.pyi +243 -0
- e2D/devices.py +19 -6
- e2D/devices.pyi +65 -0
- e2D/plots.py +55 -29
- e2D/plots.pyi +238 -0
- e2D/shapes.py +81 -44
- e2D/shapes.pyi +272 -0
- e2D/test_colors.py +122 -0
- e2D/text_renderer.py +46 -16
- e2D/text_renderer.pyi +118 -0
- e2D/types.py +58 -0
- e2D/types.pyi +61 -0
- e2D/vectors.py +153 -61
- e2D/vectors.pyi +106 -0
- e2D/winrec.py +275 -0
- e2D/winrec.pyi +87 -0
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/METADATA +95 -26
- e2d-2.0.2.dist-info/RECORD +46 -0
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/WHEEL +1 -1
- e2d-2.0.0.dist-info/RECORD +0 -26
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/licenses/LICENSE +0 -0
- {e2d-2.0.0.dist-info → e2d-2.0.2.dist-info}/top_level.txt +0 -0
e2D/cvectors.cp313-win_amd64.pyd
CHANGED
|
Binary file
|
e2D/cvectors.pyi
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type stubs for cvectors module (Cython-compiled Vector2D)
|
|
3
|
+
Provides type hints for all Vector2D operations
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Tuple
|
|
7
|
+
from .types import Number
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
|
|
11
|
+
class Vector2D:
|
|
12
|
+
"""
|
|
13
|
+
High-performance 2D vector class optimized for heavy simulations.
|
|
14
|
+
|
|
15
|
+
Uses contiguous numpy arrays and Cython for near-C performance.
|
|
16
|
+
All operations are optimized with no bounds checking and inline C math.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
data: npt.NDArray[np.float64]
|
|
20
|
+
|
|
21
|
+
def __init__(self, x: float = 0.0, y: float = 0.0) -> None:
|
|
22
|
+
"""Initialize vector with x, y components"""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
# Property accessors
|
|
26
|
+
@property
|
|
27
|
+
def x(self) -> float:
|
|
28
|
+
"""X component"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
@x.setter
|
|
32
|
+
def x(self, value: float) -> None: ...
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def y(self) -> float:
|
|
36
|
+
"""Y component"""
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
@y.setter
|
|
40
|
+
def y(self, value: float) -> None: ...
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def length(self) -> float:
|
|
44
|
+
"""Vector length (magnitude)"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def length_sqrd(self) -> float:
|
|
49
|
+
"""Squared length (faster, avoids sqrt)"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def angle(self) -> float:
|
|
54
|
+
"""Angle of this vector"""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
@angle.setter
|
|
58
|
+
def angle(self, new_angle: float) -> None:
|
|
59
|
+
"""Set angle while maintaining magnitude"""
|
|
60
|
+
...
|
|
61
|
+
|
|
62
|
+
# Methods
|
|
63
|
+
def copy(self) -> Vector2D:
|
|
64
|
+
"""Fast copy"""
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def set(self, x: float, y: float) -> None:
|
|
68
|
+
"""Set both components"""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
def iadd(self, other: Vector2D) -> None:
|
|
72
|
+
"""In-place addition"""
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
def isub(self, other: Vector2D) -> None:
|
|
76
|
+
"""In-place subtraction"""
|
|
77
|
+
...
|
|
78
|
+
|
|
79
|
+
def imul(self, scalar: float) -> None:
|
|
80
|
+
"""In-place scalar multiplication"""
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
def idiv(self, scalar: float) -> None:
|
|
84
|
+
"""In-place division"""
|
|
85
|
+
...
|
|
86
|
+
|
|
87
|
+
def imul_vec(self, other: Vector2D) -> None:
|
|
88
|
+
"""In-place component-wise multiplication"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def iadd_scalar(self, scalar: float) -> None:
|
|
92
|
+
"""In-place scalar addition"""
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
def isub_scalar(self, scalar: float) -> None:
|
|
96
|
+
"""In-place scalar subtraction"""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
def normalize(self) -> None:
|
|
100
|
+
"""Normalize in-place"""
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def clamp_inplace(self, min_val: Vector2D, max_val: Vector2D) -> None:
|
|
104
|
+
"""Clamp components in-place"""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def add(self, other: Vector2D) -> Vector2D:
|
|
108
|
+
"""Addition (returns new vector)"""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
def sub(self, other: Vector2D) -> Vector2D:
|
|
112
|
+
"""Subtraction (returns new vector)"""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def mul(self, scalar: float) -> Vector2D:
|
|
116
|
+
"""Scalar multiplication (returns new vector)"""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
def mul_vec(self, other: Vector2D) -> Vector2D:
|
|
120
|
+
"""Component-wise multiplication (returns new vector)"""
|
|
121
|
+
...
|
|
122
|
+
|
|
123
|
+
def normalized(self) -> Vector2D:
|
|
124
|
+
"""Get normalized vector (returns new)"""
|
|
125
|
+
...
|
|
126
|
+
|
|
127
|
+
def dot_product(self, other: Vector2D) -> float:
|
|
128
|
+
"""Dot product"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def distance_to(self, other: Vector2D, rooted: bool = True) -> float:
|
|
132
|
+
"""Distance to another vector"""
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
def angle_to(self, other: Vector2D) -> float:
|
|
136
|
+
"""Angle to another vector"""
|
|
137
|
+
...
|
|
138
|
+
|
|
139
|
+
def rotate(self, angle: float) -> Vector2D:
|
|
140
|
+
"""Rotate vector by angle (returns new)"""
|
|
141
|
+
...
|
|
142
|
+
|
|
143
|
+
def irotate(self, angle: float) -> None:
|
|
144
|
+
"""Rotate in-place"""
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
def lerp(self, other: Vector2D, t: float) -> Vector2D:
|
|
148
|
+
"""Linear interpolation"""
|
|
149
|
+
...
|
|
150
|
+
|
|
151
|
+
def clamp(self, min_val: Vector2D, max_val: Vector2D) -> Vector2D:
|
|
152
|
+
"""Clamp (returns new)"""
|
|
153
|
+
...
|
|
154
|
+
|
|
155
|
+
def projection(self, other: Vector2D) -> Vector2D:
|
|
156
|
+
"""Project this vector onto another"""
|
|
157
|
+
...
|
|
158
|
+
|
|
159
|
+
def reflection(self, normal: Vector2D) -> Vector2D:
|
|
160
|
+
"""Reflect vector across normal"""
|
|
161
|
+
...
|
|
162
|
+
|
|
163
|
+
# Python operator overloads
|
|
164
|
+
def __add__(self, other: Vector2D | Number) -> Vector2D: ...
|
|
165
|
+
def __sub__(self, other: Vector2D | Number) -> Vector2D: ...
|
|
166
|
+
def __mul__(self, other: Vector2D | Number) -> Vector2D: ...
|
|
167
|
+
def __truediv__(self, other: Number) -> Vector2D: ...
|
|
168
|
+
def __iadd__(self, other: Vector2D | Number) -> Vector2D: ...
|
|
169
|
+
def __isub__(self, other: Vector2D | Number) -> Vector2D: ...
|
|
170
|
+
def __imul__(self, other: Vector2D | Number) -> Vector2D: ...
|
|
171
|
+
def __itruediv__(self, other: Number) -> Vector2D: ...
|
|
172
|
+
def __neg__(self) -> Vector2D: ...
|
|
173
|
+
def __abs__(self) -> Vector2D: ...
|
|
174
|
+
def __getitem__(self, idx: int) -> float: ...
|
|
175
|
+
def __setitem__(self, idx: int, value: float) -> None: ...
|
|
176
|
+
def __str__(self) -> str: ...
|
|
177
|
+
def __repr__(self) -> str: ...
|
|
178
|
+
|
|
179
|
+
# Utility methods
|
|
180
|
+
def to_list(self) -> List[float]:
|
|
181
|
+
"""Convert to Python list"""
|
|
182
|
+
...
|
|
183
|
+
|
|
184
|
+
def to_tuple(self) -> Tuple[float, float]:
|
|
185
|
+
"""Convert to Python tuple"""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
# Class methods for common vectors
|
|
189
|
+
@staticmethod
|
|
190
|
+
def zero() -> Vector2D:
|
|
191
|
+
"""Create zero vector (0, 0)"""
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def one() -> Vector2D:
|
|
196
|
+
"""Create one vector (1, 1)"""
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def up() -> Vector2D:
|
|
201
|
+
"""Create up vector (0, 1)"""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def down() -> Vector2D:
|
|
206
|
+
"""Create down vector (0, -1)"""
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def left() -> Vector2D:
|
|
211
|
+
"""Create left vector (-1, 0)"""
|
|
212
|
+
...
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def right() -> Vector2D:
|
|
216
|
+
"""Create right vector (1, 0)"""
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def random(min_val: float = 0.0, max_val: float = 1.0) -> Vector2D:
|
|
221
|
+
"""Create random vector"""
|
|
222
|
+
...
|
|
223
|
+
|
|
224
|
+
# Batch operations for processing many vectors at once
|
|
225
|
+
def batch_add_inplace(vectors: List[Vector2D], displacement: Vector2D) -> None:
|
|
226
|
+
"""Add displacement to all vectors in-place (ultra-fast)"""
|
|
227
|
+
...
|
|
228
|
+
|
|
229
|
+
def batch_scale_inplace(vectors: List[Vector2D], scalar: float) -> None:
|
|
230
|
+
"""Scale all vectors in-place"""
|
|
231
|
+
...
|
|
232
|
+
|
|
233
|
+
def batch_normalize_inplace(vectors: List[Vector2D]) -> None:
|
|
234
|
+
"""Normalize all vectors in-place"""
|
|
235
|
+
...
|
|
236
|
+
|
|
237
|
+
def vectors_to_array(vectors: List[Vector2D]) -> npt.NDArray[np.float64]:
|
|
238
|
+
"""Convert list of vectors to numpy array (fast)"""
|
|
239
|
+
...
|
|
240
|
+
|
|
241
|
+
def array_to_vectors(arr: npt.NDArray[np.float64]) -> List[Vector2D]:
|
|
242
|
+
"""Convert numpy array to list of vectors (fast)"""
|
|
243
|
+
...
|
e2D/devices.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
import glfw
|
|
3
|
+
from .types import VectorType, WindowType
|
|
3
4
|
|
|
4
5
|
class KeyState(Enum):
|
|
5
6
|
PRESSED = 1
|
|
@@ -7,12 +8,16 @@ class KeyState(Enum):
|
|
|
7
8
|
JUST_RELEASED = 3
|
|
8
9
|
|
|
9
10
|
class Keyboard:
|
|
11
|
+
pressed: set[int]
|
|
12
|
+
just_pressed: set[int]
|
|
13
|
+
just_released: set[int]
|
|
14
|
+
|
|
10
15
|
def __init__(self) -> None:
|
|
11
16
|
self.pressed = set()
|
|
12
17
|
self.just_pressed = set()
|
|
13
18
|
self.just_released = set()
|
|
14
19
|
|
|
15
|
-
def _on_key(self, window, key, scancode, action, mods) -> None:
|
|
20
|
+
def _on_key(self, window: WindowType, key: int, scancode: int, action: int, mods: int) -> None:
|
|
16
21
|
if action == glfw.PRESS:
|
|
17
22
|
self.pressed.add(key)
|
|
18
23
|
self.just_pressed.add(key)
|
|
@@ -24,7 +29,7 @@ class Keyboard:
|
|
|
24
29
|
self.just_pressed.clear()
|
|
25
30
|
self.just_released.clear()
|
|
26
31
|
|
|
27
|
-
def get_key(self, key, state: KeyState) -> bool:
|
|
32
|
+
def get_key(self, key: int, state: KeyState|int = KeyState.PRESSED) -> bool:
|
|
28
33
|
if state == KeyState.PRESSED:
|
|
29
34
|
return key in self.pressed
|
|
30
35
|
elif state == KeyState.JUST_PRESSED:
|
|
@@ -34,6 +39,14 @@ class Keyboard:
|
|
|
34
39
|
return False
|
|
35
40
|
|
|
36
41
|
class Mouse:
|
|
42
|
+
position: VectorType
|
|
43
|
+
last_position: VectorType
|
|
44
|
+
delta: VectorType
|
|
45
|
+
scroll: VectorType
|
|
46
|
+
pressed: set[int]
|
|
47
|
+
just_pressed: set[int]
|
|
48
|
+
just_released: set[int]
|
|
49
|
+
|
|
37
50
|
def __init__(self) -> None:
|
|
38
51
|
self.position = (0, 0)
|
|
39
52
|
self.last_position = (0, 0)
|
|
@@ -43,10 +56,10 @@ class Mouse:
|
|
|
43
56
|
self.just_pressed = set()
|
|
44
57
|
self.just_released = set()
|
|
45
58
|
|
|
46
|
-
def _on_cursor_pos(self, window, x, y) -> None:
|
|
59
|
+
def _on_cursor_pos(self, window: WindowType, x: float, y: float) -> None:
|
|
47
60
|
self.position = (x, y)
|
|
48
61
|
|
|
49
|
-
def _on_mouse_button(self, window, button, action, mods) -> None:
|
|
62
|
+
def _on_mouse_button(self, window: WindowType, button: int, action: int, mods: int) -> None:
|
|
50
63
|
if action == glfw.PRESS:
|
|
51
64
|
self.pressed.add(button)
|
|
52
65
|
self.just_pressed.add(button)
|
|
@@ -54,7 +67,7 @@ class Mouse:
|
|
|
54
67
|
self.pressed.discard(button)
|
|
55
68
|
self.just_released.add(button)
|
|
56
69
|
|
|
57
|
-
def _on_scroll(self, window, xoffset, yoffset) -> None:
|
|
70
|
+
def _on_scroll(self, window: WindowType, xoffset: float, yoffset: float) -> None:
|
|
58
71
|
self.scroll = (xoffset, yoffset)
|
|
59
72
|
|
|
60
73
|
def update(self) -> None:
|
|
@@ -64,7 +77,7 @@ class Mouse:
|
|
|
64
77
|
self.just_released.clear()
|
|
65
78
|
self.scroll = (0, 0)
|
|
66
79
|
|
|
67
|
-
def get_button(self, button, state: KeyState) -> bool:
|
|
80
|
+
def get_button(self, button: int, state: KeyState) -> bool:
|
|
68
81
|
if state == KeyState.PRESSED:
|
|
69
82
|
return button in self.pressed
|
|
70
83
|
elif state == KeyState.JUST_PRESSED:
|
e2D/devices.pyi
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type stubs for devices module
|
|
3
|
+
Input device handling (Keyboard and Mouse)
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from .types import VectorType, WindowType
|
|
8
|
+
|
|
9
|
+
class KeyState(Enum):
|
|
10
|
+
"""State of a keyboard key or mouse button"""
|
|
11
|
+
PRESSED: int
|
|
12
|
+
JUST_PRESSED: int
|
|
13
|
+
JUST_RELEASED: int
|
|
14
|
+
|
|
15
|
+
class Keyboard:
|
|
16
|
+
"""Keyboard input handler"""
|
|
17
|
+
pressed: set[int]
|
|
18
|
+
just_pressed: set[int]
|
|
19
|
+
just_released: set[int]
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None: ...
|
|
22
|
+
|
|
23
|
+
def _on_key(self, window: WindowType, key: int, scancode: int, action: int, mods: int) -> None:
|
|
24
|
+
"""Internal key event handler"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def update(self) -> None:
|
|
28
|
+
"""Update keyboard state (call once per frame)"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
def get_key(self, key: int, state: KeyState|int) -> bool:
|
|
32
|
+
"""Check if a key is in the given state"""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
class Mouse:
|
|
36
|
+
"""Mouse input handler"""
|
|
37
|
+
position: VectorType
|
|
38
|
+
last_position: VectorType
|
|
39
|
+
delta: VectorType
|
|
40
|
+
scroll: VectorType
|
|
41
|
+
pressed: set[int]
|
|
42
|
+
just_pressed: set[int]
|
|
43
|
+
just_released: set[int]
|
|
44
|
+
|
|
45
|
+
def __init__(self) -> None: ...
|
|
46
|
+
|
|
47
|
+
def _on_cursor_pos(self, window: WindowType, x: float, y: float) -> None:
|
|
48
|
+
"""Internal cursor position event handler"""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
def _on_mouse_button(self, window: WindowType, button: int, action: int, mods: int) -> None:
|
|
52
|
+
"""Internal mouse button event handler"""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
def _on_scroll(self, window: WindowType, xoffset: float, yoffset: float) -> None:
|
|
56
|
+
"""Internal scroll event handler"""
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
def update(self) -> None:
|
|
60
|
+
"""Update mouse state (call once per frame)"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
def get_button(self, button: int, state: KeyState | int) -> bool:
|
|
64
|
+
"""Check if a mouse button is in the given state"""
|
|
65
|
+
...
|
e2D/plots.py
CHANGED
|
@@ -5,6 +5,10 @@ import struct
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from enum import Enum
|
|
7
7
|
import os
|
|
8
|
+
from .commons import set_uniform_block_binding
|
|
9
|
+
from .types import ColorType, ComputeShaderType, ComputeShaderType, Number, VAOType, VectorType, ContextType, ProgramType, BufferType, ArrayLike
|
|
10
|
+
from .colors import normalize_color
|
|
11
|
+
from .color_defs import GRAY10, GRAY50, WHITE, RED, CYAN
|
|
8
12
|
|
|
9
13
|
class ShaderManager:
|
|
10
14
|
"""Cache and manage shader files for the plots module."""
|
|
@@ -25,14 +29,14 @@ class ShaderManager:
|
|
|
25
29
|
return ShaderManager._cache[path]
|
|
26
30
|
|
|
27
31
|
@staticmethod
|
|
28
|
-
def create_program(ctx:
|
|
32
|
+
def create_program(ctx: ContextType, vertex_path: str, fragment_path: str) -> ProgramType:
|
|
29
33
|
"""Create a program from shader files."""
|
|
30
34
|
vertex_shader = ShaderManager.load_shader(vertex_path)
|
|
31
35
|
fragment_shader = ShaderManager.load_shader(fragment_path)
|
|
32
36
|
return ctx.program(vertex_shader=vertex_shader, fragment_shader=fragment_shader)
|
|
33
37
|
|
|
34
38
|
@staticmethod
|
|
35
|
-
def create_compute(ctx:
|
|
39
|
+
def create_compute(ctx: ContextType, compute_path: str) -> ComputeShaderType:
|
|
36
40
|
"""Create a compute shader from file."""
|
|
37
41
|
compute_shader = ShaderManager.load_shader(compute_path)
|
|
38
42
|
return ctx.compute_shader(compute_shader)
|
|
@@ -48,7 +52,15 @@ class View2D:
|
|
|
48
52
|
float aspect; // 24
|
|
49
53
|
float _pad; // 28
|
|
50
54
|
"""
|
|
51
|
-
|
|
55
|
+
ctx: ContextType
|
|
56
|
+
binding: int
|
|
57
|
+
center: ArrayLike
|
|
58
|
+
zoom: float
|
|
59
|
+
aspect: float
|
|
60
|
+
resolution: ArrayLike
|
|
61
|
+
buffer: BufferType
|
|
62
|
+
|
|
63
|
+
def __init__(self, ctx: ContextType, binding: int = 0) -> None:
|
|
52
64
|
self.ctx = ctx
|
|
53
65
|
self.binding = binding
|
|
54
66
|
self.center = np.array([0.0, 0.0], dtype='f4')
|
|
@@ -60,7 +72,7 @@ class View2D:
|
|
|
60
72
|
self.buffer.bind_to_uniform_block(self.binding)
|
|
61
73
|
self.update_buffer()
|
|
62
74
|
|
|
63
|
-
def update_win_size(self, width:
|
|
75
|
+
def update_win_size(self, width: float, height: float) -> None:
|
|
64
76
|
self.resolution = np.array([width, height], dtype='f4')
|
|
65
77
|
self.aspect = width / height if height > 0 else 1.0
|
|
66
78
|
self.update_buffer()
|
|
@@ -99,23 +111,23 @@ class View2D:
|
|
|
99
111
|
|
|
100
112
|
@dataclass
|
|
101
113
|
class PlotSettings:
|
|
102
|
-
bg_color:
|
|
114
|
+
bg_color: ColorType = GRAY10
|
|
103
115
|
show_axis: bool = True
|
|
104
|
-
axis_color:
|
|
116
|
+
axis_color: ColorType = GRAY50
|
|
105
117
|
axis_width: float = 2.0
|
|
106
118
|
show_grid: bool = True
|
|
107
|
-
grid_color:
|
|
119
|
+
grid_color: ColorType = (0.2, 0.2, 0.2, 1.0)
|
|
108
120
|
grid_spacing: float = 1.0
|
|
109
121
|
|
|
110
122
|
@dataclass
|
|
111
123
|
class CurveSettings:
|
|
112
|
-
color:
|
|
124
|
+
color: ColorType = WHITE
|
|
113
125
|
width: float = 2.0
|
|
114
126
|
count: int = 1024
|
|
115
127
|
|
|
116
128
|
@dataclass
|
|
117
129
|
class ImplicitSettings:
|
|
118
|
-
color:
|
|
130
|
+
color: ColorType = CYAN
|
|
119
131
|
thickness: float = 2.0
|
|
120
132
|
|
|
121
133
|
class LineType(Enum):
|
|
@@ -127,18 +139,32 @@ class LineType(Enum):
|
|
|
127
139
|
|
|
128
140
|
@dataclass
|
|
129
141
|
class StreamSettings:
|
|
130
|
-
point_color:
|
|
142
|
+
point_color: ColorType = RED
|
|
131
143
|
point_radius: float = 5.0
|
|
132
144
|
show_points: bool = True
|
|
133
145
|
round_points: bool = True
|
|
134
146
|
line_type: LineType = LineType.DIRECT
|
|
135
|
-
line_color:
|
|
147
|
+
line_color: ColorType = RED
|
|
136
148
|
line_width: float = 2.0
|
|
137
149
|
curve_segments: int = 10
|
|
138
150
|
|
|
139
151
|
class Plot2D:
|
|
140
152
|
"""A specific rectangular area on the screen for plotting."""
|
|
141
|
-
|
|
153
|
+
ctx: ContextType
|
|
154
|
+
top_left: VectorType
|
|
155
|
+
bottom_right: VectorType
|
|
156
|
+
settings: PlotSettings
|
|
157
|
+
width: Number
|
|
158
|
+
height: Number
|
|
159
|
+
view: View2D
|
|
160
|
+
viewport: tuple[int, int, int, int]
|
|
161
|
+
grid_prog: ProgramType
|
|
162
|
+
grid_quad: BufferType
|
|
163
|
+
grid_vao: VAOType
|
|
164
|
+
is_dragging: bool
|
|
165
|
+
last_mouse_pos: VectorType
|
|
166
|
+
|
|
167
|
+
def __init__(self, ctx: ContextType, top_left: VectorType, bottom_right: VectorType, settings: Optional[PlotSettings] = None) -> None:
|
|
142
168
|
self.ctx = ctx
|
|
143
169
|
self.top_left = top_left
|
|
144
170
|
self.bottom_right = bottom_right
|
|
@@ -150,7 +176,7 @@ class Plot2D:
|
|
|
150
176
|
self.view = View2D(ctx)
|
|
151
177
|
self.view.update_win_size(self.width, self.height)
|
|
152
178
|
|
|
153
|
-
self.viewport = (top_left[0], 1080 - bottom_right[1], self.width, self.height)
|
|
179
|
+
self.viewport = (int(top_left[0]), 1080 - int(bottom_right[1]), int(self.width), int(self.height))
|
|
154
180
|
self._init_grid_renderer()
|
|
155
181
|
|
|
156
182
|
self.is_dragging = False
|
|
@@ -163,27 +189,27 @@ class Plot2D:
|
|
|
163
189
|
"shaders/plot_grid_fragment.glsl"
|
|
164
190
|
)
|
|
165
191
|
try:
|
|
166
|
-
self.grid_prog
|
|
192
|
+
set_uniform_block_binding(self.grid_prog, 'View', 0)
|
|
167
193
|
except:
|
|
168
194
|
pass
|
|
169
195
|
self.grid_quad = self.ctx.buffer(np.array([-1,-1, 1,-1, -1,1, 1,1], dtype='f4'))
|
|
170
196
|
self.grid_vao = self.ctx.simple_vertex_array(self.grid_prog, self.grid_quad, "in_vert")
|
|
171
197
|
|
|
172
|
-
def set_rect(self, top_left:
|
|
198
|
+
def set_rect(self, top_left: VectorType, bottom_right: VectorType) -> None:
|
|
173
199
|
self.top_left = top_left
|
|
174
200
|
self.bottom_right = bottom_right
|
|
175
201
|
self.width = bottom_right[0] - top_left[0]
|
|
176
202
|
self.height = bottom_right[1] - top_left[1]
|
|
177
203
|
self.view.update_win_size(self.width, self.height)
|
|
178
204
|
|
|
179
|
-
def update_window_size(self, win_width: int, win_height: int):
|
|
205
|
+
def update_window_size(self, win_width: int, win_height: int) -> None:
|
|
180
206
|
x = self.top_left[0]
|
|
181
207
|
w = self.width
|
|
182
208
|
h = self.height
|
|
183
|
-
y = win_height - self.bottom_right[1]
|
|
184
|
-
self.viewport = (x, y, w, h)
|
|
209
|
+
y = win_height - self.bottom_right[1]
|
|
210
|
+
self.viewport = (int(x), int(y), int(w), int(h))
|
|
185
211
|
|
|
186
|
-
def render(self, draw_callback):
|
|
212
|
+
def render(self, draw_callback) -> None:
|
|
187
213
|
self.ctx.viewport = self.viewport
|
|
188
214
|
self.ctx.scissor = self.viewport
|
|
189
215
|
self.ctx.clear(*self.settings.bg_color)
|
|
@@ -220,7 +246,7 @@ class Plot2D:
|
|
|
220
246
|
|
|
221
247
|
class GpuStream:
|
|
222
248
|
"""Ring-buffer on GPU for high-performance point streaming."""
|
|
223
|
-
def __init__(self, ctx:
|
|
249
|
+
def __init__(self, ctx: ContextType, capacity: int = 100000, settings: Optional[StreamSettings] = None) -> None:
|
|
224
250
|
self.ctx = ctx
|
|
225
251
|
self.capacity = capacity
|
|
226
252
|
self.settings = settings if settings else StreamSettings()
|
|
@@ -237,7 +263,7 @@ class GpuStream:
|
|
|
237
263
|
"shaders/stream_fragment.glsl"
|
|
238
264
|
)
|
|
239
265
|
try:
|
|
240
|
-
self.prog
|
|
266
|
+
set_uniform_block_binding(self.prog, 'View', 0)
|
|
241
267
|
except:
|
|
242
268
|
pass
|
|
243
269
|
self.vao = ctx.vertex_array(self.prog, [])
|
|
@@ -311,7 +337,7 @@ class GpuStream:
|
|
|
311
337
|
"""
|
|
312
338
|
)
|
|
313
339
|
try:
|
|
314
|
-
self.smooth_prog
|
|
340
|
+
set_uniform_block_binding(self.smooth_prog, 'View', 0)
|
|
315
341
|
except:
|
|
316
342
|
pass
|
|
317
343
|
self.smooth_vao = ctx.vertex_array(self.smooth_prog, [])
|
|
@@ -374,7 +400,7 @@ class GpuStream:
|
|
|
374
400
|
self.prog['round_points'] = self.settings.round_points
|
|
375
401
|
self.vao.render(moderngl.POINTS, vertices=self.size)
|
|
376
402
|
|
|
377
|
-
def shift_points(self, offset:
|
|
403
|
+
def shift_points(self, offset: VectorType) -> None:
|
|
378
404
|
"""Shifts all existing points by the given offset using a Compute Shader."""
|
|
379
405
|
if not hasattr(self, 'shift_prog'):
|
|
380
406
|
self.shift_prog = ShaderManager.create_compute(
|
|
@@ -392,7 +418,7 @@ class GpuStream:
|
|
|
392
418
|
|
|
393
419
|
class ComputeCurve:
|
|
394
420
|
"""Parametric curve p(t) evaluated entirely on GPU."""
|
|
395
|
-
def __init__(self, ctx:
|
|
421
|
+
def __init__(self, ctx: ContextType, func_body: str, t_range: tuple, count: int = 1024, settings: Optional[CurveSettings] = None):
|
|
396
422
|
self.ctx = ctx
|
|
397
423
|
self.count = count
|
|
398
424
|
self.t_range = t_range
|
|
@@ -432,7 +458,7 @@ class ComputeCurve:
|
|
|
432
458
|
"shaders/curve_fragment.glsl"
|
|
433
459
|
)
|
|
434
460
|
try:
|
|
435
|
-
self.render_prog
|
|
461
|
+
set_uniform_block_binding(self.render_prog, 'View', 0)
|
|
436
462
|
except:
|
|
437
463
|
pass
|
|
438
464
|
self.vao = ctx.simple_vertex_array(self.render_prog, self.vbo, "in_pos")
|
|
@@ -454,7 +480,7 @@ class ComputeCurve:
|
|
|
454
480
|
|
|
455
481
|
class ImplicitPlot:
|
|
456
482
|
"""Rendering of f(x,y)=0 via Fragment Shader and SDF."""
|
|
457
|
-
def __init__(self, ctx:
|
|
483
|
+
def __init__(self, ctx: ContextType, func_body: str, settings: Optional[ImplicitSettings] = None):
|
|
458
484
|
self.ctx = ctx
|
|
459
485
|
self.settings = settings if settings else ImplicitSettings()
|
|
460
486
|
|
|
@@ -506,7 +532,7 @@ class ImplicitPlot:
|
|
|
506
532
|
|
|
507
533
|
self.prog = ctx.program(vertex_shader=vs_src, fragment_shader=fs_src)
|
|
508
534
|
try:
|
|
509
|
-
self.prog
|
|
535
|
+
set_uniform_block_binding(self.prog, 'View', 0)
|
|
510
536
|
except:
|
|
511
537
|
pass
|
|
512
538
|
self.vao = ctx.simple_vertex_array(self.prog, self.quad, "in_vert")
|
|
@@ -518,7 +544,7 @@ class ImplicitPlot:
|
|
|
518
544
|
|
|
519
545
|
class SegmentDisplay:
|
|
520
546
|
"""Simple 7-segment display renderer for numbers."""
|
|
521
|
-
def __init__(self, ctx:
|
|
547
|
+
def __init__(self, ctx: ContextType):
|
|
522
548
|
self.ctx = ctx
|
|
523
549
|
self.prog = ShaderManager.create_program(
|
|
524
550
|
ctx,
|
|
@@ -543,7 +569,7 @@ class SegmentDisplay:
|
|
|
543
569
|
'.': [7]
|
|
544
570
|
}
|
|
545
571
|
|
|
546
|
-
def draw_number(self, text: str, x: float, y: float, size: float = 20.0, color:
|
|
572
|
+
def draw_number(self, text: str, x: float, y: float, size: float = 20.0, color: ColorType = WHITE):
|
|
547
573
|
vertices = []
|
|
548
574
|
cursor_x = x
|
|
549
575
|
w = size * 0.5
|