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
|
Binary file
|
e2D/cvectors.pxd
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Cython header file for Vector2D class
|
|
5
|
+
Allows other Cython modules to use this without Python overhead
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
cimport numpy as cnp
|
|
9
|
+
|
|
10
|
+
ctypedef cnp.float64_t DTYPE_t
|
|
11
|
+
|
|
12
|
+
cdef class Vector2D:
|
|
13
|
+
cdef public cnp.ndarray data
|
|
14
|
+
cdef DTYPE_t* _data_ptr
|
|
15
|
+
|
|
16
|
+
# Fast inline methods (no Python overhead)
|
|
17
|
+
cdef inline double _length_squared(self) nogil
|
|
18
|
+
cdef inline double _length(self) nogil
|
|
19
|
+
cdef inline double _dot(self, Vector2D other) nogil
|
|
20
|
+
|
|
21
|
+
# cpdef methods (callable from Python and Cython)
|
|
22
|
+
cpdef Vector2D copy(self)
|
|
23
|
+
cpdef void set(self, double x, double y)
|
|
24
|
+
cpdef void iadd(self, Vector2D other)
|
|
25
|
+
cpdef void isub(self, Vector2D other)
|
|
26
|
+
cpdef void imul(self, double scalar)
|
|
27
|
+
cpdef void idiv(self, double scalar)
|
|
28
|
+
cpdef void imul_vec(self, Vector2D other)
|
|
29
|
+
cpdef void iadd_scalar(self, double scalar)
|
|
30
|
+
cpdef void isub_scalar(self, double scalar)
|
|
31
|
+
cpdef void normalize(self)
|
|
32
|
+
cpdef void clamp_inplace(self, Vector2D min_val, Vector2D max_val)
|
|
33
|
+
cpdef Vector2D add(self, Vector2D other)
|
|
34
|
+
cpdef Vector2D sub(self, Vector2D other)
|
|
35
|
+
cpdef Vector2D mul(self, double scalar)
|
|
36
|
+
cpdef Vector2D mul_vec(self, Vector2D other)
|
|
37
|
+
cpdef Vector2D normalized(self)
|
|
38
|
+
cpdef double dot_product(self, Vector2D other)
|
|
39
|
+
cpdef double distance_to(self, Vector2D other, bint rooted=*)
|
|
40
|
+
cpdef double angle_to(self, Vector2D other)
|
|
41
|
+
cpdef Vector2D rotate(self, double angle)
|
|
42
|
+
cpdef void irotate(self, double angle)
|
|
43
|
+
cpdef Vector2D lerp(self, Vector2D other, double t)
|
|
44
|
+
cpdef Vector2D clamp(self, Vector2D min_val, Vector2D max_val)
|
|
45
|
+
cpdef Vector2D projection(self, Vector2D other)
|
|
46
|
+
cpdef Vector2D reflection(self, Vector2D normal)
|
|
47
|
+
cpdef list to_list(self)
|
|
48
|
+
cpdef tuple to_tuple(self)
|
|
49
|
+
|
|
50
|
+
# Batch operation functions
|
|
51
|
+
cpdef void batch_add_inplace(list vectors, Vector2D displacement)
|
|
52
|
+
cpdef void batch_scale_inplace(list vectors, double scalar)
|
|
53
|
+
cpdef void batch_normalize_inplace(list vectors)
|
|
54
|
+
cpdef cnp.ndarray vectors_to_array(list vectors)
|
|
55
|
+
cpdef list array_to_vectors(cnp.ndarray arr)
|
|
56
|
+
|
e2D/cvectors.pyx
ADDED
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
# cython: language_level=3
|
|
2
|
+
# cython: boundscheck=False
|
|
3
|
+
# cython: wraparound=False
|
|
4
|
+
# cython: nonecheck=False
|
|
5
|
+
# cython: cdivision=True
|
|
6
|
+
# cython: initializedcheck=False
|
|
7
|
+
# cython: overflowcheck=False
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
Ultra-optimized Vector2D class for heavy simulations
|
|
11
|
+
Uses Cython with numpy backend for maximum performance
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
cimport numpy as cnp
|
|
16
|
+
cimport cython
|
|
17
|
+
from libc.math cimport sqrt, sin, cos, atan2, floor, ceil, fabs, fmod, pow as c_pow
|
|
18
|
+
from libc.stdlib cimport rand, RAND_MAX
|
|
19
|
+
|
|
20
|
+
cnp.import_array()
|
|
21
|
+
|
|
22
|
+
cdef class Vector2D:
|
|
23
|
+
"""
|
|
24
|
+
High-performance 2D vector class optimized for heavy simulations.
|
|
25
|
+
|
|
26
|
+
Uses contiguous numpy arrays and Cython for near-C performance.
|
|
27
|
+
All operations are optimized with no bounds checking and inline C math.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __cinit__(self, double x=0.0, double y=0.0):
|
|
31
|
+
"""Initialize vector with x, y components"""
|
|
32
|
+
self.data = np.array([x, y], dtype=np.float64)
|
|
33
|
+
self._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(self.data)
|
|
34
|
+
|
|
35
|
+
# Property accessors with direct memory access
|
|
36
|
+
@property
|
|
37
|
+
def x(self):
|
|
38
|
+
return self._data_ptr[0]
|
|
39
|
+
|
|
40
|
+
@x.setter
|
|
41
|
+
def x(self, double value):
|
|
42
|
+
self._data_ptr[0] = value
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def y(self):
|
|
46
|
+
return self._data_ptr[1]
|
|
47
|
+
|
|
48
|
+
@y.setter
|
|
49
|
+
def y(self, double value):
|
|
50
|
+
self._data_ptr[1] = value
|
|
51
|
+
|
|
52
|
+
# Fast inline operations
|
|
53
|
+
@cython.cdivision(True)
|
|
54
|
+
cdef inline double _length_squared(self) nogil:
|
|
55
|
+
"""Calculate squared length (no sqrt, faster)"""
|
|
56
|
+
return self._data_ptr[0] * self._data_ptr[0] + self._data_ptr[1] * self._data_ptr[1]
|
|
57
|
+
|
|
58
|
+
@cython.cdivision(True)
|
|
59
|
+
cdef inline double _length(self) nogil:
|
|
60
|
+
"""Calculate length"""
|
|
61
|
+
return sqrt(self._data_ptr[0] * self._data_ptr[0] + self._data_ptr[1] * self._data_ptr[1])
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def length(self):
|
|
65
|
+
"""Vector length (magnitude)"""
|
|
66
|
+
return self._length()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def length_sqrd(self):
|
|
70
|
+
"""Squared length (faster, avoids sqrt)"""
|
|
71
|
+
return self._length_squared()
|
|
72
|
+
|
|
73
|
+
cpdef Vector2D copy(self):
|
|
74
|
+
"""Fast copy"""
|
|
75
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
76
|
+
result.data = self.data.copy()
|
|
77
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
# In-place operations (fastest)
|
|
81
|
+
cpdef void set(self, double x, double y):
|
|
82
|
+
"""Set both components"""
|
|
83
|
+
self._data_ptr[0] = x
|
|
84
|
+
self._data_ptr[1] = y
|
|
85
|
+
|
|
86
|
+
cpdef void iadd(self, Vector2D other):
|
|
87
|
+
"""In-place addition"""
|
|
88
|
+
self._data_ptr[0] += other._data_ptr[0]
|
|
89
|
+
self._data_ptr[1] += other._data_ptr[1]
|
|
90
|
+
|
|
91
|
+
cpdef void isub(self, Vector2D other):
|
|
92
|
+
"""In-place subtraction"""
|
|
93
|
+
self._data_ptr[0] -= other._data_ptr[0]
|
|
94
|
+
self._data_ptr[1] -= other._data_ptr[1]
|
|
95
|
+
|
|
96
|
+
cpdef void imul(self, double scalar):
|
|
97
|
+
"""In-place scalar multiplication"""
|
|
98
|
+
self._data_ptr[0] *= scalar
|
|
99
|
+
self._data_ptr[1] *= scalar
|
|
100
|
+
|
|
101
|
+
cpdef void idiv(self, double scalar):
|
|
102
|
+
"""In-place division"""
|
|
103
|
+
cdef double inv = 1.0 / scalar
|
|
104
|
+
self._data_ptr[0] *= inv
|
|
105
|
+
self._data_ptr[1] *= inv
|
|
106
|
+
|
|
107
|
+
cpdef void imul_vec(self, Vector2D other):
|
|
108
|
+
"""In-place component-wise multiplication"""
|
|
109
|
+
self._data_ptr[0] *= other._data_ptr[0]
|
|
110
|
+
self._data_ptr[1] *= other._data_ptr[1]
|
|
111
|
+
|
|
112
|
+
cpdef void iadd_scalar(self, double scalar):
|
|
113
|
+
"""In-place scalar addition"""
|
|
114
|
+
self._data_ptr[0] += scalar
|
|
115
|
+
self._data_ptr[1] += scalar
|
|
116
|
+
|
|
117
|
+
cpdef void isub_scalar(self, double scalar):
|
|
118
|
+
"""In-place scalar subtraction"""
|
|
119
|
+
self._data_ptr[0] -= scalar
|
|
120
|
+
self._data_ptr[1] -= scalar
|
|
121
|
+
|
|
122
|
+
cpdef void normalize(self):
|
|
123
|
+
"""Normalize in-place"""
|
|
124
|
+
cdef double length
|
|
125
|
+
cdef double inv_length
|
|
126
|
+
length = self._length()
|
|
127
|
+
if length > 0.0:
|
|
128
|
+
inv_length = 1.0 / length
|
|
129
|
+
self._data_ptr[0] *= inv_length
|
|
130
|
+
self._data_ptr[1] *= inv_length
|
|
131
|
+
|
|
132
|
+
cpdef void clamp_inplace(self, Vector2D min_val, Vector2D max_val):
|
|
133
|
+
"""Clamp components in-place"""
|
|
134
|
+
if self._data_ptr[0] < min_val._data_ptr[0]:
|
|
135
|
+
self._data_ptr[0] = min_val._data_ptr[0]
|
|
136
|
+
elif self._data_ptr[0] > max_val._data_ptr[0]:
|
|
137
|
+
self._data_ptr[0] = max_val._data_ptr[0]
|
|
138
|
+
|
|
139
|
+
if self._data_ptr[1] < min_val._data_ptr[1]:
|
|
140
|
+
self._data_ptr[1] = min_val._data_ptr[1]
|
|
141
|
+
elif self._data_ptr[1] > max_val._data_ptr[1]:
|
|
142
|
+
self._data_ptr[1] = max_val._data_ptr[1]
|
|
143
|
+
|
|
144
|
+
# New vector operations (return new instances)
|
|
145
|
+
cpdef Vector2D add(self, Vector2D other):
|
|
146
|
+
"""Addition (returns new vector)"""
|
|
147
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
148
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
149
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
150
|
+
result._data_ptr[0] = self._data_ptr[0] + other._data_ptr[0]
|
|
151
|
+
result._data_ptr[1] = self._data_ptr[1] + other._data_ptr[1]
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
cpdef Vector2D sub(self, Vector2D other):
|
|
155
|
+
"""Subtraction (returns new vector)"""
|
|
156
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
157
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
158
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
159
|
+
result._data_ptr[0] = self._data_ptr[0] - other._data_ptr[0]
|
|
160
|
+
result._data_ptr[1] = self._data_ptr[1] - other._data_ptr[1]
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
cpdef Vector2D mul(self, double scalar):
|
|
164
|
+
"""Scalar multiplication (returns new vector)"""
|
|
165
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
166
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
167
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
168
|
+
result._data_ptr[0] = self._data_ptr[0] * scalar
|
|
169
|
+
result._data_ptr[1] = self._data_ptr[1] * scalar
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
cpdef Vector2D mul_vec(self, Vector2D other):
|
|
173
|
+
"""Component-wise multiplication (returns new vector)"""
|
|
174
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
175
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
176
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
177
|
+
result._data_ptr[0] = self._data_ptr[0] * other._data_ptr[0]
|
|
178
|
+
result._data_ptr[1] = self._data_ptr[1] * other._data_ptr[1]
|
|
179
|
+
return result
|
|
180
|
+
|
|
181
|
+
cpdef Vector2D normalized(self):
|
|
182
|
+
"""Get normalized vector (returns new)"""
|
|
183
|
+
cdef double length
|
|
184
|
+
cdef double inv_length
|
|
185
|
+
cdef Vector2D result
|
|
186
|
+
|
|
187
|
+
length = self._length()
|
|
188
|
+
result = Vector2D.__new__(Vector2D)
|
|
189
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
190
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
191
|
+
|
|
192
|
+
if length > 0.0:
|
|
193
|
+
inv_length = 1.0 / length
|
|
194
|
+
result._data_ptr[0] = self._data_ptr[0] * inv_length
|
|
195
|
+
result._data_ptr[1] = self._data_ptr[1] * inv_length
|
|
196
|
+
else:
|
|
197
|
+
result._data_ptr[0] = 0.0
|
|
198
|
+
result._data_ptr[1] = 0.0
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
@cython.cdivision(True)
|
|
202
|
+
cdef inline double _dot(self, Vector2D other) nogil:
|
|
203
|
+
"""Dot product (inline, no GIL)"""
|
|
204
|
+
return self._data_ptr[0] * other._data_ptr[0] + self._data_ptr[1] * other._data_ptr[1]
|
|
205
|
+
|
|
206
|
+
cpdef double dot_product(self, Vector2D other):
|
|
207
|
+
"""Dot product"""
|
|
208
|
+
return self._dot(other)
|
|
209
|
+
|
|
210
|
+
cpdef double distance_to(self, Vector2D other, bint rooted=True):
|
|
211
|
+
"""Distance to another vector"""
|
|
212
|
+
cdef double dx
|
|
213
|
+
cdef double dy
|
|
214
|
+
cdef double dist_sq
|
|
215
|
+
|
|
216
|
+
dx = self._data_ptr[0] - other._data_ptr[0]
|
|
217
|
+
dy = self._data_ptr[1] - other._data_ptr[1]
|
|
218
|
+
dist_sq = dx * dx + dy * dy
|
|
219
|
+
if rooted:
|
|
220
|
+
return sqrt(dist_sq)
|
|
221
|
+
return dist_sq
|
|
222
|
+
|
|
223
|
+
cpdef double angle_to(self, Vector2D other):
|
|
224
|
+
"""Angle to another vector"""
|
|
225
|
+
cdef double dx
|
|
226
|
+
cdef double dy
|
|
227
|
+
|
|
228
|
+
dx = other._data_ptr[0] - self._data_ptr[0]
|
|
229
|
+
dy = other._data_ptr[1] - self._data_ptr[1]
|
|
230
|
+
return atan2(dy, dx)
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def angle(self):
|
|
234
|
+
"""Angle of this vector"""
|
|
235
|
+
return atan2(self._data_ptr[1], self._data_ptr[0])
|
|
236
|
+
|
|
237
|
+
@angle.setter
|
|
238
|
+
def angle(self, double new_angle):
|
|
239
|
+
"""Set angle while maintaining magnitude"""
|
|
240
|
+
cdef double mag
|
|
241
|
+
|
|
242
|
+
mag = self._length()
|
|
243
|
+
self._data_ptr[0] = mag * cos(new_angle)
|
|
244
|
+
self._data_ptr[1] = mag * sin(new_angle)
|
|
245
|
+
|
|
246
|
+
cpdef Vector2D rotate(self, double angle):
|
|
247
|
+
"""Rotate vector by angle (returns new)"""
|
|
248
|
+
cdef double cos_a
|
|
249
|
+
cdef double sin_a
|
|
250
|
+
cdef Vector2D result
|
|
251
|
+
|
|
252
|
+
cos_a = cos(angle)
|
|
253
|
+
sin_a = sin(angle)
|
|
254
|
+
result = Vector2D.__new__(Vector2D)
|
|
255
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
256
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
257
|
+
result._data_ptr[0] = self._data_ptr[0] * cos_a - self._data_ptr[1] * sin_a
|
|
258
|
+
result._data_ptr[1] = self._data_ptr[0] * sin_a + self._data_ptr[1] * cos_a
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
cpdef void irotate(self, double angle):
|
|
262
|
+
"""Rotate in-place"""
|
|
263
|
+
cdef double cos_a
|
|
264
|
+
cdef double sin_a
|
|
265
|
+
cdef double new_x
|
|
266
|
+
cdef double new_y
|
|
267
|
+
cos_a = cos(angle)
|
|
268
|
+
sin_a = sin(angle)
|
|
269
|
+
new_x = self._data_ptr[0] * cos_a - self._data_ptr[1] * sin_a
|
|
270
|
+
new_y = self._data_ptr[0] * sin_a + self._data_ptr[1] * cos_a
|
|
271
|
+
self._data_ptr[0] = new_x
|
|
272
|
+
self._data_ptr[1] = new_y
|
|
273
|
+
|
|
274
|
+
cpdef Vector2D lerp(self, Vector2D other, double t):
|
|
275
|
+
"""Linear interpolation"""
|
|
276
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
277
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
278
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
279
|
+
result._data_ptr[0] = self._data_ptr[0] + (other._data_ptr[0] - self._data_ptr[0]) * t
|
|
280
|
+
result._data_ptr[1] = self._data_ptr[1] + (other._data_ptr[1] - self._data_ptr[1]) * t
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
cpdef Vector2D clamp(self, Vector2D min_val, Vector2D max_val):
|
|
284
|
+
"""Clamp (returns new)"""
|
|
285
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
286
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
287
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
288
|
+
|
|
289
|
+
result._data_ptr[0] = self._data_ptr[0]
|
|
290
|
+
if result._data_ptr[0] < min_val._data_ptr[0]:
|
|
291
|
+
result._data_ptr[0] = min_val._data_ptr[0]
|
|
292
|
+
elif result._data_ptr[0] > max_val._data_ptr[0]:
|
|
293
|
+
result._data_ptr[0] = max_val._data_ptr[0]
|
|
294
|
+
|
|
295
|
+
result._data_ptr[1] = self._data_ptr[1]
|
|
296
|
+
if result._data_ptr[1] < min_val._data_ptr[1]:
|
|
297
|
+
result._data_ptr[1] = min_val._data_ptr[1]
|
|
298
|
+
elif result._data_ptr[1] > max_val._data_ptr[1]:
|
|
299
|
+
result._data_ptr[1] = max_val._data_ptr[1]
|
|
300
|
+
|
|
301
|
+
return result
|
|
302
|
+
|
|
303
|
+
cpdef Vector2D projection(self, Vector2D other):
|
|
304
|
+
"""Project this vector onto another"""
|
|
305
|
+
cdef double dot
|
|
306
|
+
cdef double other_len_sq
|
|
307
|
+
cdef double scalar
|
|
308
|
+
|
|
309
|
+
dot = self._dot(other)
|
|
310
|
+
other_len_sq = other._length_squared()
|
|
311
|
+
if other_len_sq > 0.0:
|
|
312
|
+
scalar = dot / other_len_sq
|
|
313
|
+
return other.mul(scalar)
|
|
314
|
+
return Vector2D(0.0, 0.0)
|
|
315
|
+
|
|
316
|
+
cpdef Vector2D reflection(self, Vector2D normal):
|
|
317
|
+
"""Reflect vector across normal"""
|
|
318
|
+
cdef double dot
|
|
319
|
+
cdef Vector2D result
|
|
320
|
+
|
|
321
|
+
dot = self._dot(normal)
|
|
322
|
+
result = Vector2D.__new__(Vector2D)
|
|
323
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
324
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
325
|
+
result._data_ptr[0] = self._data_ptr[0] - 2.0 * dot * normal._data_ptr[0]
|
|
326
|
+
result._data_ptr[1] = self._data_ptr[1] - 2.0 * dot * normal._data_ptr[1]
|
|
327
|
+
return result
|
|
328
|
+
|
|
329
|
+
# Python operator overloads
|
|
330
|
+
def __add__(self, other):
|
|
331
|
+
cdef Vector2D result
|
|
332
|
+
|
|
333
|
+
if isinstance(other, Vector2D):
|
|
334
|
+
return self.add(other)
|
|
335
|
+
elif isinstance(other, (int, float)):
|
|
336
|
+
result = self.copy()
|
|
337
|
+
result.iadd_scalar(other)
|
|
338
|
+
return result
|
|
339
|
+
return NotImplemented
|
|
340
|
+
|
|
341
|
+
def __sub__(self, other):
|
|
342
|
+
cdef Vector2D result
|
|
343
|
+
|
|
344
|
+
if isinstance(other, Vector2D):
|
|
345
|
+
return self.sub(other)
|
|
346
|
+
elif isinstance(other, (int, float)):
|
|
347
|
+
result = self.copy()
|
|
348
|
+
result.isub_scalar(other)
|
|
349
|
+
return result
|
|
350
|
+
return NotImplemented
|
|
351
|
+
|
|
352
|
+
def __mul__(self, other):
|
|
353
|
+
if isinstance(other, (int, float)):
|
|
354
|
+
return self.mul(other)
|
|
355
|
+
elif isinstance(other, Vector2D):
|
|
356
|
+
return self.mul_vec(other)
|
|
357
|
+
return NotImplemented
|
|
358
|
+
|
|
359
|
+
def __truediv__(self, other):
|
|
360
|
+
cdef Vector2D result
|
|
361
|
+
|
|
362
|
+
if isinstance(other, (int, float)):
|
|
363
|
+
if other != 0.0:
|
|
364
|
+
result = self.copy()
|
|
365
|
+
result.idiv(other)
|
|
366
|
+
return result
|
|
367
|
+
return NotImplemented
|
|
368
|
+
|
|
369
|
+
def __iadd__(self, other):
|
|
370
|
+
if isinstance(other, Vector2D):
|
|
371
|
+
self.iadd(other)
|
|
372
|
+
elif isinstance(other, (int, float)):
|
|
373
|
+
self.iadd_scalar(other)
|
|
374
|
+
return self
|
|
375
|
+
|
|
376
|
+
def __isub__(self, other):
|
|
377
|
+
if isinstance(other, Vector2D):
|
|
378
|
+
self.isub(other)
|
|
379
|
+
elif isinstance(other, (int, float)):
|
|
380
|
+
self.isub_scalar(other)
|
|
381
|
+
return self
|
|
382
|
+
|
|
383
|
+
def __imul__(self, other):
|
|
384
|
+
if isinstance(other, (int, float)):
|
|
385
|
+
self.imul(other)
|
|
386
|
+
elif isinstance(other, Vector2D):
|
|
387
|
+
self.imul_vec(other)
|
|
388
|
+
return self
|
|
389
|
+
|
|
390
|
+
def __itruediv__(self, other):
|
|
391
|
+
if isinstance(other, (int, float)) and other != 0.0:
|
|
392
|
+
self.idiv(other)
|
|
393
|
+
return self
|
|
394
|
+
|
|
395
|
+
def __neg__(self):
|
|
396
|
+
return self.mul(-1.0)
|
|
397
|
+
|
|
398
|
+
def __abs__(self):
|
|
399
|
+
cdef Vector2D result = Vector2D.__new__(Vector2D)
|
|
400
|
+
result.data = np.empty(2, dtype=np.float64)
|
|
401
|
+
result._data_ptr = <DTYPE_t*> cnp.PyArray_DATA(result.data)
|
|
402
|
+
result._data_ptr[0] = fabs(self._data_ptr[0])
|
|
403
|
+
result._data_ptr[1] = fabs(self._data_ptr[1])
|
|
404
|
+
return result
|
|
405
|
+
|
|
406
|
+
def __getitem__(self, int idx):
|
|
407
|
+
if idx == 0:
|
|
408
|
+
return self._data_ptr[0]
|
|
409
|
+
elif idx == 1:
|
|
410
|
+
return self._data_ptr[1]
|
|
411
|
+
raise IndexError("Index out of range")
|
|
412
|
+
|
|
413
|
+
def __setitem__(self, int idx, double value):
|
|
414
|
+
if idx == 0:
|
|
415
|
+
self._data_ptr[0] = value
|
|
416
|
+
elif idx == 1:
|
|
417
|
+
self._data_ptr[1] = value
|
|
418
|
+
else:
|
|
419
|
+
raise IndexError("Index out of range")
|
|
420
|
+
|
|
421
|
+
def __str__(self):
|
|
422
|
+
return f"Vector2D({self._data_ptr[0]:.6f}, {self._data_ptr[1]:.6f})"
|
|
423
|
+
|
|
424
|
+
def __repr__(self):
|
|
425
|
+
return f"Vector2D({self._data_ptr[0]}, {self._data_ptr[1]})"
|
|
426
|
+
|
|
427
|
+
# Utility methods
|
|
428
|
+
cpdef list to_list(self):
|
|
429
|
+
"""Convert to Python list"""
|
|
430
|
+
return [self._data_ptr[0], self._data_ptr[1]]
|
|
431
|
+
|
|
432
|
+
cpdef tuple to_tuple(self):
|
|
433
|
+
"""Convert to Python tuple"""
|
|
434
|
+
return (self._data_ptr[0], self._data_ptr[1])
|
|
435
|
+
|
|
436
|
+
# Class methods for common vectors
|
|
437
|
+
@staticmethod
|
|
438
|
+
def zero():
|
|
439
|
+
return Vector2D(0.0, 0.0)
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def one():
|
|
443
|
+
return Vector2D(1.0, 1.0)
|
|
444
|
+
|
|
445
|
+
@staticmethod
|
|
446
|
+
def up():
|
|
447
|
+
return Vector2D(0.0, 1.0)
|
|
448
|
+
|
|
449
|
+
@staticmethod
|
|
450
|
+
def down():
|
|
451
|
+
return Vector2D(0.0, -1.0)
|
|
452
|
+
|
|
453
|
+
@staticmethod
|
|
454
|
+
def left():
|
|
455
|
+
return Vector2D(-1.0, 0.0)
|
|
456
|
+
|
|
457
|
+
@staticmethod
|
|
458
|
+
def right():
|
|
459
|
+
return Vector2D(1.0, 0.0)
|
|
460
|
+
|
|
461
|
+
@staticmethod
|
|
462
|
+
def random(double min_val=0.0, double max_val=1.0):
|
|
463
|
+
"""Create random vector"""
|
|
464
|
+
cdef double range_val
|
|
465
|
+
cdef double rx
|
|
466
|
+
cdef double ry
|
|
467
|
+
|
|
468
|
+
range_val = max_val - min_val
|
|
469
|
+
rx = (<double>rand() / <double>RAND_MAX) * range_val + min_val
|
|
470
|
+
ry = (<double>rand() / <double>RAND_MAX) * range_val + min_val
|
|
471
|
+
return Vector2D(rx, ry)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
# Batch operations for processing many vectors at once
|
|
475
|
+
@cython.boundscheck(False)
|
|
476
|
+
@cython.wraparound(False)
|
|
477
|
+
cpdef void batch_add_inplace(list vectors, Vector2D displacement):
|
|
478
|
+
"""Add displacement to all vectors in-place (ultra-fast)"""
|
|
479
|
+
cdef Vector2D vec
|
|
480
|
+
cdef Py_ssize_t i
|
|
481
|
+
cdef Py_ssize_t n = len(vectors)
|
|
482
|
+
|
|
483
|
+
for i in range(n):
|
|
484
|
+
vec = <Vector2D>vectors[i]
|
|
485
|
+
vec._data_ptr[0] += displacement._data_ptr[0]
|
|
486
|
+
vec._data_ptr[1] += displacement._data_ptr[1]
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@cython.boundscheck(False)
|
|
490
|
+
@cython.wraparound(False)
|
|
491
|
+
cpdef void batch_scale_inplace(list vectors, double scalar):
|
|
492
|
+
"""Scale all vectors in-place"""
|
|
493
|
+
cdef Vector2D vec
|
|
494
|
+
cdef Py_ssize_t i
|
|
495
|
+
cdef Py_ssize_t n = len(vectors)
|
|
496
|
+
|
|
497
|
+
for i in range(n):
|
|
498
|
+
vec = <Vector2D>vectors[i]
|
|
499
|
+
vec._data_ptr[0] *= scalar
|
|
500
|
+
vec._data_ptr[1] *= scalar
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
@cython.boundscheck(False)
|
|
504
|
+
@cython.wraparound(False)
|
|
505
|
+
cpdef void batch_normalize_inplace(list vectors):
|
|
506
|
+
"""Normalize all vectors in-place"""
|
|
507
|
+
cdef Vector2D vec
|
|
508
|
+
cdef Py_ssize_t i
|
|
509
|
+
cdef Py_ssize_t n
|
|
510
|
+
cdef double length
|
|
511
|
+
cdef double inv_length
|
|
512
|
+
|
|
513
|
+
n = len(vectors)
|
|
514
|
+
|
|
515
|
+
for i in range(n):
|
|
516
|
+
vec = <Vector2D>vectors[i]
|
|
517
|
+
length = sqrt(vec._data_ptr[0] * vec._data_ptr[0] + vec._data_ptr[1] * vec._data_ptr[1])
|
|
518
|
+
if length > 0.0:
|
|
519
|
+
inv_length = 1.0 / length
|
|
520
|
+
vec._data_ptr[0] *= inv_length
|
|
521
|
+
vec._data_ptr[1] *= inv_length
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@cython.boundscheck(False)
|
|
525
|
+
@cython.wraparound(False)
|
|
526
|
+
cpdef cnp.ndarray[DTYPE_t, ndim=2] vectors_to_array(list vectors):
|
|
527
|
+
"""Convert list of vectors to numpy array (fast)"""
|
|
528
|
+
cdef Py_ssize_t n
|
|
529
|
+
cdef cnp.ndarray[DTYPE_t, ndim=2] result
|
|
530
|
+
cdef Vector2D vec
|
|
531
|
+
cdef Py_ssize_t i
|
|
532
|
+
|
|
533
|
+
n = len(vectors)
|
|
534
|
+
result = np.empty((n, 2), dtype=np.float64)
|
|
535
|
+
|
|
536
|
+
for i in range(n):
|
|
537
|
+
vec = <Vector2D>vectors[i]
|
|
538
|
+
result[i, 0] = vec._data_ptr[0]
|
|
539
|
+
result[i, 1] = vec._data_ptr[1]
|
|
540
|
+
|
|
541
|
+
return result
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@cython.boundscheck(False)
|
|
545
|
+
@cython.wraparound(False)
|
|
546
|
+
cpdef list array_to_vectors(cnp.ndarray[DTYPE_t, ndim=2] arr):
|
|
547
|
+
"""Convert numpy array to list of vectors (fast)"""
|
|
548
|
+
cdef Py_ssize_t n
|
|
549
|
+
cdef list result
|
|
550
|
+
cdef Vector2D vec
|
|
551
|
+
cdef Py_ssize_t i
|
|
552
|
+
|
|
553
|
+
n = arr.shape[0]
|
|
554
|
+
result = []
|
|
555
|
+
|
|
556
|
+
for i in range(n):
|
|
557
|
+
vec = Vector2D(arr[i, 0], arr[i, 1])
|
|
558
|
+
result.append(vec)
|
|
559
|
+
|
|
560
|
+
return result
|
|
561
|
+
|
e2D/devices.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
import glfw
|
|
3
|
+
|
|
4
|
+
class KeyState(Enum):
|
|
5
|
+
PRESSED = 1
|
|
6
|
+
JUST_PRESSED = 2
|
|
7
|
+
JUST_RELEASED = 3
|
|
8
|
+
|
|
9
|
+
class Keyboard:
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
self.pressed = set()
|
|
12
|
+
self.just_pressed = set()
|
|
13
|
+
self.just_released = set()
|
|
14
|
+
|
|
15
|
+
def _on_key(self, window, key, scancode, action, mods) -> None:
|
|
16
|
+
if action == glfw.PRESS:
|
|
17
|
+
self.pressed.add(key)
|
|
18
|
+
self.just_pressed.add(key)
|
|
19
|
+
elif action == glfw.RELEASE:
|
|
20
|
+
self.pressed.discard(key)
|
|
21
|
+
self.just_released.add(key)
|
|
22
|
+
|
|
23
|
+
def update(self) -> None:
|
|
24
|
+
self.just_pressed.clear()
|
|
25
|
+
self.just_released.clear()
|
|
26
|
+
|
|
27
|
+
def get_key(self, key, state: KeyState) -> bool:
|
|
28
|
+
if state == KeyState.PRESSED:
|
|
29
|
+
return key in self.pressed
|
|
30
|
+
elif state == KeyState.JUST_PRESSED:
|
|
31
|
+
return key in self.just_pressed
|
|
32
|
+
elif state == KeyState.JUST_RELEASED:
|
|
33
|
+
return key in self.just_released
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
class Mouse:
|
|
37
|
+
def __init__(self) -> None:
|
|
38
|
+
self.position = (0, 0)
|
|
39
|
+
self.last_position = (0, 0)
|
|
40
|
+
self.delta = (0, 0)
|
|
41
|
+
self.scroll = (0, 0)
|
|
42
|
+
self.pressed = set()
|
|
43
|
+
self.just_pressed = set()
|
|
44
|
+
self.just_released = set()
|
|
45
|
+
|
|
46
|
+
def _on_cursor_pos(self, window, x, y) -> None:
|
|
47
|
+
self.position = (x, y)
|
|
48
|
+
|
|
49
|
+
def _on_mouse_button(self, window, button, action, mods) -> None:
|
|
50
|
+
if action == glfw.PRESS:
|
|
51
|
+
self.pressed.add(button)
|
|
52
|
+
self.just_pressed.add(button)
|
|
53
|
+
elif action == glfw.RELEASE:
|
|
54
|
+
self.pressed.discard(button)
|
|
55
|
+
self.just_released.add(button)
|
|
56
|
+
|
|
57
|
+
def _on_scroll(self, window, xoffset, yoffset) -> None:
|
|
58
|
+
self.scroll = (xoffset, yoffset)
|
|
59
|
+
|
|
60
|
+
def update(self) -> None:
|
|
61
|
+
self.delta = (self.position[0] - self.last_position[0], self.position[1] - self.last_position[1])
|
|
62
|
+
self.last_position = self.position
|
|
63
|
+
self.just_pressed.clear()
|
|
64
|
+
self.just_released.clear()
|
|
65
|
+
self.scroll = (0, 0)
|
|
66
|
+
|
|
67
|
+
def get_button(self, button, state: KeyState) -> bool:
|
|
68
|
+
if state == KeyState.PRESSED:
|
|
69
|
+
return button in self.pressed
|
|
70
|
+
elif state == KeyState.JUST_PRESSED:
|
|
71
|
+
return button in self.just_pressed
|
|
72
|
+
elif state == KeyState.JUST_RELEASED:
|
|
73
|
+
return button in self.just_released
|
|
74
|
+
return False
|