ncca-ngl 0.3.4__py3-none-any.whl → 0.5.0__py3-none-any.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.
Files changed (56) hide show
  1. ncca/ngl/PrimData/pack_arrays.py +2 -3
  2. ncca/ngl/__init__.py +3 -4
  3. ncca/ngl/base_mesh.py +28 -20
  4. ncca/ngl/image.py +1 -3
  5. ncca/ngl/mat2.py +79 -53
  6. ncca/ngl/mat3.py +104 -185
  7. ncca/ngl/mat4.py +144 -309
  8. ncca/ngl/prim_data.py +42 -36
  9. ncca/ngl/primitives.py +2 -2
  10. ncca/ngl/pyside_event_handling_mixin.py +0 -108
  11. ncca/ngl/quaternion.py +69 -36
  12. ncca/ngl/shader.py +0 -116
  13. ncca/ngl/shader_program.py +94 -117
  14. ncca/ngl/texture.py +5 -2
  15. ncca/ngl/util.py +7 -0
  16. ncca/ngl/vec2.py +58 -292
  17. ncca/ngl/vec2_array.py +79 -28
  18. ncca/ngl/vec3.py +59 -340
  19. ncca/ngl/vec3_array.py +76 -23
  20. ncca/ngl/vec4.py +90 -190
  21. ncca/ngl/vec4_array.py +78 -27
  22. ncca/ngl/vector_base.py +542 -0
  23. ncca/ngl/webgpu/__init__.py +20 -0
  24. ncca/ngl/webgpu/__main__.py +640 -0
  25. ncca/ngl/webgpu/__main__.py.backup +640 -0
  26. ncca/ngl/webgpu/base_webgpu_pipeline.py +354 -0
  27. ncca/ngl/webgpu/custom_shader_pipeline.py +288 -0
  28. ncca/ngl/webgpu/instanced_geometry_pipeline.py +594 -0
  29. ncca/ngl/webgpu/line_pipeline.py +405 -0
  30. ncca/ngl/webgpu/pipeline_factory.py +190 -0
  31. ncca/ngl/webgpu/pipeline_shaders.py +497 -0
  32. ncca/ngl/webgpu/point_list_pipeline.py +349 -0
  33. ncca/ngl/webgpu/point_pipeline.py +336 -0
  34. ncca/ngl/webgpu/triangle_pipeline.py +419 -0
  35. ncca/ngl/webgpu/webgpu_constants.py +31 -0
  36. ncca/ngl/webgpu/webgpu_widget.py +322 -0
  37. ncca/ngl/webgpu/wip/REFACTORING_SUMMARY.md +82 -0
  38. ncca/ngl/webgpu/wip/UNIFIED_SYSTEM.md +314 -0
  39. ncca/ngl/webgpu/wip/buffer_manager.py +396 -0
  40. ncca/ngl/webgpu/wip/pipeline_config.py +463 -0
  41. ncca/ngl/webgpu/wip/shader_constants.py +328 -0
  42. ncca/ngl/webgpu/wip/shader_templates.py +563 -0
  43. ncca/ngl/webgpu/wip/unified_examples.py +390 -0
  44. ncca/ngl/webgpu/wip/unified_factory.py +449 -0
  45. ncca/ngl/webgpu/wip/unified_pipeline.py +469 -0
  46. ncca/ngl/widgets/__init__.py +18 -2
  47. ncca/ngl/widgets/__main__.py +2 -1
  48. ncca/ngl/widgets/lookatwidget.py +2 -1
  49. ncca/ngl/widgets/mat4widget.py +2 -2
  50. ncca/ngl/widgets/vec2widget.py +1 -1
  51. ncca/ngl/widgets/vec3widget.py +1 -0
  52. {ncca_ngl-0.3.4.dist-info → ncca_ngl-0.5.0.dist-info}/METADATA +3 -2
  53. ncca_ngl-0.5.0.dist-info/RECORD +105 -0
  54. ncca/ngl/widgets/transformation_widget.py +0 -299
  55. ncca_ngl-0.3.4.dist-info/RECORD +0 -82
  56. {ncca_ngl-0.3.4.dist-info → ncca_ngl-0.5.0.dist-info}/WHEEL +0 -0
ncca/ngl/vec3.py CHANGED
@@ -1,401 +1,120 @@
1
1
  """
2
2
  Simple float only Vec3 class for 3D graphics, very similar to the pyngl ones
3
+ NumPy-based implementation with VectorBase inheritance for code reuse.
3
4
  """
4
5
 
5
- import ctypes
6
6
  import math
7
+ from typing import Union
7
8
 
8
9
  import numpy as np
9
10
 
10
11
  from .util import clamp
12
+ from .vector_base import VectorBase, _create_properties
11
13
 
12
14
 
13
- class Vec3:
15
+ class Vec3(VectorBase["Vec3"]):
14
16
  """
15
- A simple 3D vector class for 3D graphics, I use slots to fix the attributes to x,y,z
17
+ A simple 3D vector class for 3D graphics, using numpy for efficient operations.
18
+
16
19
  Attributes:
17
20
  x (float): The x-coordinate of the vector.
18
21
  y (float): The y-coordinate of the vector.
19
22
  z (float): The z-coordinate of the vector.
20
-
21
23
  """
22
24
 
23
- __slots__ = ["_x", "_y", "_z"] # fix the attributes to x,y,z
24
-
25
- def __init__(self, x=0.0, y=0.0, z=0.0):
26
- """
27
- Initializes a new instance of the Vec3 class.
28
-
29
- Args:
30
- x (float, optional): The x-coordinate of the vector. Defaults to 0.0.
31
- y (float, optional): The y-coordinate of the vector. Defaults to 0.0.
32
- z (float, optional): The z-coordinate of the vector. Defaults to 0.0.
33
- """
34
- self._x = x # x component of vector : float
35
- self._y = y # y component of vector : float
36
- self._z = z # z component of vector : float
37
-
38
- @classmethod
39
- def sizeof(cls):
40
- return 3 * ctypes.sizeof(ctypes.c_float)
41
-
42
- def __iter__(self):
43
- """
44
- Make the Vec3 class iterable.
45
- Yields:
46
- float: The x, y, and z components of the vector.
47
- """
48
- yield self.x
49
- yield self.y
50
- yield self.z
51
-
52
- def __getitem__(self, index):
53
- """
54
- Get the component of the vector at the given index.
55
- Args:
56
- index (int): The index of the component (0 for x, 1 for y, 2 for z).
57
- Returns:
58
- float: The value of the component at the given index.
59
- Raises:
60
- IndexError: If the index is out of range.
61
- """
62
- components = [self.x, self.y, self.z]
63
- try:
64
- return components[index]
65
- except IndexError:
66
- raise IndexError("Index out of range. Valid indices are 0, 1, and 2.")
67
-
68
- def _validate_and_set(self, v, name):
69
- """
70
- check if v is a float or int
71
- Args:
72
- v (number): The value to check.
73
- Raises:
74
- ValueError: If v is not a float or int.
75
- """
76
- if not isinstance(v, (int, float, np.float32)):
77
- raise ValueError("need float or int")
78
- else:
79
- setattr(self, name, v)
80
-
81
- def copy(self) -> "Vec3":
82
- """
83
- Create a copy of the current vector.
84
- Returns:
85
- Vec3: A new instance of Vec3 with the same x, y, and z values.
86
- """
87
- return Vec3(self.x, self.y, self.z)
88
-
89
- def __add__(self, rhs):
90
- """
91
- vector addition a+b
92
-
93
- Args:
94
- rhs (Vec3): The right-hand side vector to add.
95
- Returns:
96
- Vec3: A new vector that is the result of adding this vector and the rhs vector.
97
- """
98
- r = Vec3()
99
- r.x = self.x + rhs.x
100
- r.y = self.y + rhs.y
101
- r.z = self.z + rhs.z
102
- return r
103
-
104
- def __iadd__(self, rhs):
105
- """
106
- vector addition a+=b
107
-
108
- Args:
109
- rhs (Vec3): The right-hand side vector to add.
110
- Returns:
111
- Vec3: returns this vector after adding the rhs vector.
112
- """
113
- self.x += rhs.x
114
- self.y += rhs.y
115
- self.z += rhs.z
116
- return self
117
-
118
- def __sub__(self, rhs):
119
- """
120
- vector subtraction a-b
121
-
122
- Args:
123
- rhs (Vec3): The right-hand side vector to subtract.
124
- Returns:
125
- Vec3: A new vector that is the result of subtracting this vector and the rhs vector.
126
- """
127
- r = Vec3()
128
- r.x = self.x - rhs.x
129
- r.y = self.y - rhs.y
130
- r.z = self.z - rhs.z
131
- return r
132
-
133
- def __isub__(self, rhs):
134
- """
135
- vector subtraction a-=b
136
-
137
- Args:
138
- rhs (Vec3): The right-hand side vector to add.
139
- Returns:
140
- Vec3: returns this vector after subtracting the rhs vector.
141
- """
142
- self.x -= rhs.x
143
- self.y -= rhs.y
144
- self.z -= rhs.z
145
- return self
146
-
147
- def __eq__(self, rhs):
148
- """
149
- vector comparison a==b using math.isclose not we only compare to 6 decimal places
150
- Args:
151
- rhs (Vec3): The right-hand side vector to compare.
152
- Returns:
153
- bool: True if the vectors are close, False otherwise.
154
- NotImplemented: If the right-hand side is not a Vec3.
155
- """
156
-
157
- if not isinstance(rhs, Vec3):
158
- return NotImplemented
159
- return math.isclose(self.x, rhs.x) and math.isclose(self.y, rhs.y) and math.isclose(self.z, rhs.z)
160
-
161
- def __neq__(self, rhs):
162
- """
163
- vector comparison a!=b using math.isclose not we only compare to 6 decimal places
164
- Args:
165
- rhs (Vec3): The right-hand side vector to compare.
166
- Returns:
167
- bool: True if the vectors are not close, False otherwise.
168
- NotImplemented: If the right-hand side is not a Vec3.
169
- """
170
- if not isinstance(rhs, Vec3):
171
- return NotImplemented
172
- return not (math.isclose(self.x, rhs.x) and math.isclose(self.y, rhs.y) and math.isclose(self.z, rhs.z))
173
-
174
- def __neg__(self):
175
- """
176
- negate a vector -a
177
- """
178
- self.x = -self.x
179
- self.y = -self.y
180
- self.z = -self.z
181
- return self
182
-
183
- def set(self, x, y, z):
184
- """
185
- set the x,y,z values of the vector
186
- Args:
187
- x (float): The x-coordinate of the vector.
188
- y (float): The y-coordinate of the vector.
189
- z (float): The z-coordinate of the vector.
190
- Raises :
191
- ValueError: if x,y,z are not float
192
- """
193
- try:
194
- self.x = float(x)
195
- self.y = float(y)
196
- self.z = float(z)
197
- except ValueError:
198
- raise ValueError(f"Vec3.set {x=} {y=} {z=} all need to be float")
25
+ DIMENSION = 3
26
+ COMPONENT_NAMES = ("x", "y", "z")
27
+ DEFAULT_VALUES = (0.0, 0.0, 0.0)
199
28
 
200
- def dot(self, rhs):
201
- """
202
- dot product of two vectors a.b
203
- Args:
204
- rhs (Vec3): The right-hand side vector to dot product with.
205
- """
206
- return self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
29
+ __slots__ = ["_data"]
207
30
 
208
- def length(self):
31
+ def cross(self, rhs: "Vec3") -> "Vec3":
209
32
  """
210
- length of vector
211
- Returns:
212
- float: The length of the vector.
213
- """
214
- return math.sqrt(self.x**2 + self.y**2 + self.z**2)
215
-
216
- def length_squared(self):
217
- """
218
- length of vector squared sometimes used to avoid the sqrt for performance
219
- Returns:
220
- float: The length of the vector squared
221
- """
222
- return self.x**2 + self.y**2 + self.z**2
33
+ Cross product of two vectors a x b.
223
34
 
224
- def inner(self, rhs):
225
- """
226
- inner product of two vectors a.b
227
- Args:
228
- rhs (Vec3): The right-hand side vector to inner product with.
229
- Returns:
230
- float: The inner product of the two vectors.
231
- """
232
- return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z)
233
-
234
- def null(self):
235
- """
236
- set the vector to zero
237
- """
238
- self.x = 0.0
239
- self.y = 0.0
240
- self.z = 0.0
241
-
242
- def cross(self, rhs):
243
- """
244
- cross product of two vectors a x b
245
35
  Args:
246
36
  rhs (Vec3): The right-hand side vector to cross product with.
37
+
247
38
  Returns:
248
39
  Vec3: A new vector that is the result of the cross product.
249
40
  """
250
- return Vec3(
251
- self.y * rhs.z - self.z * rhs.y,
252
- self.z * rhs.x - self.x * rhs.z,
253
- self.x * rhs.y - self.y * rhs.x,
254
- )
41
+ result = Vec3()
42
+ result._data = np.cross(self._data, rhs._data)
43
+ return result
255
44
 
256
- def normalize(self):
45
+ def reflect(self, n: "Vec3") -> "Vec3":
257
46
  """
258
- normalize the vector to unit length
259
- Returns:
260
- Vec3: A new vector that is the result of normalizing this vector.
261
- Raises:
262
- ZeroDivisionError: If the length of the vector is zero.
263
- """
264
- vector_length = self.length()
265
- try:
266
- self.x /= vector_length
267
- self.y /= vector_length
268
- self.z /= vector_length
269
- except ZeroDivisionError:
270
- raise ZeroDivisionError(
271
- f"Vec3.normalize {vector_length} length is zero most likely calling normalize on a zero vector"
272
- )
273
- return self
47
+ Reflect a vector about a normal.
274
48
 
275
- def reflect(self, n):
276
- """
277
- reflect a vector about a normal
278
49
  Args:
279
50
  n (Vec3): The normal to reflect about.
51
+
280
52
  Returns:
281
53
  Vec3: A new vector that is the result of reflecting this vector about the normal.
282
54
  """
283
55
  d = self.dot(n)
284
- # I - 2.0 * dot(N, I) * N
285
- return Vec3(self.x - 2.0 * d * n.x, self.y - 2.0 * d * n.y, self.z - 2.0 * d * n.z)
56
+ # I - 2.0 * dot(N, I) * N
57
+ result = Vec3()
58
+ result._data = self._data - 2.0 * d * n._data
59
+ return result
286
60
 
287
- def clamp(self, low, high):
61
+ def outer(self, rhs: "Vec3"):
288
62
  """
289
- clamp the vector to a range
290
- Args:
291
- low (float): The low end of the range.
292
- high (float): The high end of the range.
63
+ Outer product of two vectors a x b.
293
64
 
294
- """
295
- self.x = clamp(self.x, low, high)
296
- self.y = clamp(self.y, low, high)
297
- self.z = clamp(self.z, low, high)
298
-
299
- def __repr__(self):
300
- "object representation for debugging"
301
- return f"Vec3 [{self.x},{self.y},{self.z}]"
302
-
303
- def __str__(self):
304
- "object representation for debugging"
305
- return f"[{self.x},{self.y},{self.z}]"
306
-
307
- def outer(self, rhs):
308
- """
309
- outer product of two vectors a x b
310
65
  Args:
311
66
  rhs (Vec3): The right-hand side vector to outer product with.
67
+
312
68
  Returns:
313
69
  Mat3: A new 3x3 matrix that is the result of the outer product.
314
70
  """
315
71
  from .mat3 import Mat3
316
72
 
317
- return Mat3.from_list([
318
- [self.x * rhs.x, self.x * rhs.y, self.x * rhs.z],
319
- [self.y * rhs.x, self.y * rhs.y, self.y * rhs.z],
320
- [self.z * rhs.x, self.z * rhs.y, self.z * rhs.z],
321
- ])
322
-
323
- def __mul__(self, rhs):
324
- """
325
- piecewise scalar multiplication
326
- Args:
327
- rhs (float): The scalar to multiply by.
328
- Returns:
329
- Vec3: A new vector that is the result of multiplying this vector by the scalar.
330
- Raises:
331
- ValueError: If the right-hand side is not a float.
332
- """
333
- if isinstance(rhs, (float, int)):
334
- return Vec3(self.x * rhs, self.y * rhs, self.z * rhs)
335
- else:
336
- raise ValueError(f"can only do piecewise multiplication with a scalar {rhs=}")
337
-
338
- def __rmul__(self, rhs):
339
- """
340
- piecewise scalar multiplication
341
- Args:
342
- rhs (float): The scalar to multiply by.
343
- Returns:
344
- Vec3: A new vector that is the result of multiplying this vector by the scalar.
345
- Raises:
346
- ValueError: If the right-hand side is not a float.
347
- """
348
- return self * rhs
349
-
350
- def __truediv__(self, rhs):
351
- """
352
- piecewise scalar division
353
- Args:
354
- rhs (float): The scalar to divide by.
355
- Returns:
356
- Vec3: A new vector that is the result of dividing this vector by the scalar.
357
- Raises:
358
- ValueError: If the right-hand side is not a float.
359
- """
360
- if isinstance(rhs, (float, int)):
361
- return Vec3(self.x / rhs, self.y / rhs, self.z / rhs)
362
- elif isinstance(rhs, Vec3):
363
- return Vec3(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z)
364
- else:
365
- raise ValueError(f"can only do piecewise division with a scalar {rhs=}")
73
+ result = Mat3()
74
+ result.m = np.outer(self._data, rhs._data).astype(np.float64)
75
+ return result
366
76
 
367
77
  def __matmul__(self, rhs):
368
78
  """
369
- "Vec3 @ Mat3 matrix multiplication"
79
+ Vec3 @ Mat3 matrix multiplication.
80
+
370
81
  Args:
371
82
  rhs (Mat3): The matrix to multiply by.
83
+
372
84
  Returns:
373
85
  Vec3: A new vector that is the result of multiplying this vector by the matrix.
374
86
  """
375
- return Vec3(
376
- self.x * rhs.m[0][0] + self.y * rhs.m[1][0] + self.z * rhs.m[2][0],
377
- self.x * rhs.m[0][1] + self.y * rhs.m[1][1] + self.z * rhs.m[2][1],
378
- self.x * rhs.m[0][2] + self.y * rhs.m[1][2] + self.z * rhs.m[2][2],
379
- )
380
-
381
- def to_list(self):
382
- return [self.x, self.y, self.z]
87
+ result = Vec3()
88
+ result._data = rhs.m.T @ self._data # More efficient
89
+ return result
383
90
 
384
- def to_numpy(self):
385
- return np.array([self.x, self.y, self.z])
91
+ def set(self, *args: float) -> None:
92
+ """
93
+ Set the x,y,z values of the vector.
386
94
 
95
+ Args:
96
+ *args: Component values (x, y, z).
387
97
 
388
- # Helper function to create properties
389
- def _create_property(attr_name):
390
- def getter(self):
391
- return getattr(self, f"_{attr_name}")
98
+ Raises:
99
+ ValueError: If wrong number of arguments or they are not floats.
100
+ """
101
+ if len(args) != 3:
102
+ raise ValueError(f"Vec3.set requires 3 arguments, got {len(args)}")
103
+ try:
104
+ self._data[0] = float(args[0])
105
+ self._data[1] = float(args[1])
106
+ self._data[2] = float(args[2])
107
+ except ValueError:
108
+ raise ValueError(f"Vec3.set {args=} all need to be float")
392
109
 
393
- def setter(self, value):
394
- self._validate_and_set(value, f"_{attr_name}")
110
+ def __repr__(self) -> str:
111
+ """Object representation for debugging."""
112
+ return f"Vec3 [{self._data[0]},{self._data[1]},{self._data[2]}]"
395
113
 
396
- return property(getter, setter)
114
+ def __str__(self) -> str:
115
+ """String representation of the vector."""
116
+ return f"[{self._data[0]},{self._data[1]},{self._data[2]}]"
397
117
 
398
118
 
399
- # Dynamically add properties for x, y, z
400
- for attr in ["x", "y", "z"]:
401
- setattr(Vec3, attr, _create_property(attr))
119
+ # Add properties for x, y, z components
120
+ _create_properties(Vec3)
ncca/ngl/vec3_array.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """
2
2
  A container for ngl.Vec3 objects that mimics some of the behavior of a std::vector
3
+ Optimized for graphics APIs with contiguous numpy storage
3
4
  """
4
5
 
5
6
  import numpy as np
@@ -9,7 +10,8 @@ from .vec3 import Vec3
9
10
 
10
11
  class Vec3Array:
11
12
  """
12
- A class to hold a list of Vec3 objects and perform operations on them.
13
+ A class to hold Vec3 data in contiguous memory for efficient GPU transfer.
14
+ Internally uses a numpy array of shape (N, 3) for optimal performance.
13
15
  """
14
16
 
15
17
  def __init__(self, values=None):
@@ -22,27 +24,41 @@ class Vec3Array:
22
24
  If an iterable, it's initialized with the Vec3s from the iterable.
23
25
  Defaults to None (an empty array).
24
26
  """
25
- self._data = []
26
- if values is not None:
27
- if isinstance(values, int):
28
- self._data = [Vec3() for _ in range(values)]
29
- else:
30
- for v in values:
31
- if not isinstance(v, Vec3):
32
- raise TypeError("All elements must be of type Vec3")
33
- self._data.append(v)
27
+ if values is None:
28
+ # Empty array - start with shape (0, 3)
29
+ self._data = np.zeros((0, 3), dtype=np.float64)
30
+ elif isinstance(values, int):
31
+ # Initialize N default Vec3s (0, 0, 0)
32
+ self._data = np.zeros((values, 3), dtype=np.float64)
33
+ else:
34
+ # Initialize from iterable of Vec3 objects
35
+ vec_list = []
36
+ for v in values:
37
+ if not isinstance(v, Vec3):
38
+ raise TypeError("All elements must be of type Vec3")
39
+ vec_list.append([v.x, v.y, v.z])
40
+ self._data = np.array(vec_list, dtype=np.float64)
34
41
 
35
42
  def __getitem__(self, index):
36
43
  """
37
44
  Get the Vec3 at the specified index.
38
45
 
39
46
  Args:
40
- index (int): The index of the element.
47
+ index (int | slice): The index or slice of the element(s).
41
48
 
42
49
  Returns:
43
50
  Vec3: The Vec3 object at the given index.
44
- """
45
- return self._data[index]
51
+ Vec3Array: A new Vec3Array if slicing.
52
+ """
53
+ if isinstance(index, slice):
54
+ # Return a new Vec3Array with sliced data
55
+ result = Vec3Array()
56
+ result._data = self._data[index].copy()
57
+ return result
58
+ else:
59
+ # Return a single Vec3
60
+ row = self._data[index]
61
+ return Vec3(row[0], row[1], row[2])
46
62
 
47
63
  def __setitem__(self, index, value):
48
64
  """
@@ -54,7 +70,7 @@ class Vec3Array:
54
70
  """
55
71
  if not isinstance(value, Vec3):
56
72
  raise TypeError("Only Vec3 objects can be assigned")
57
- self._data[index] = value
73
+ self._data[index] = [value.x, value.y, value.z]
58
74
 
59
75
  def __len__(self):
60
76
  """
@@ -64,9 +80,25 @@ class Vec3Array:
64
80
 
65
81
  def __iter__(self):
66
82
  """
67
- Return an iterator for the array.
83
+ Return an iterator that yields Vec3 objects.
84
+ """
85
+ for i in range(len(self._data)):
86
+ row = self._data[i]
87
+ yield Vec3(row[0], row[1], row[2])
88
+
89
+ def __eq__(self, other):
90
+ """
91
+ Compare two Vec3Array instances for equality.
92
+
93
+ Args:
94
+ other: Another Vec3Array instance to compare with.
95
+
96
+ Returns:
97
+ bool: True if the arrays contain the same data, False otherwise.
68
98
  """
69
- return iter(self._data)
99
+ if not isinstance(other, Vec3Array):
100
+ return NotImplemented
101
+ return np.array_equal(self._data, other._data)
70
102
 
71
103
  def append(self, value):
72
104
  """
@@ -77,7 +109,8 @@ class Vec3Array:
77
109
  """
78
110
  if not isinstance(value, Vec3):
79
111
  raise TypeError("Only Vec3 objects can be appended")
80
- self._data.append(value)
112
+ new_row = np.array([[value.x, value.y, value.z]], dtype=np.float64)
113
+ self._data = np.vstack([self._data, new_row])
81
114
 
82
115
  def extend(self, values):
83
116
  """
@@ -89,10 +122,17 @@ class Vec3Array:
89
122
  Raises:
90
123
  TypeError: If any element in values is not a Vec3.
91
124
  """
125
+ vec_list = []
92
126
  for v in values:
93
127
  if not isinstance(v, Vec3):
94
128
  raise TypeError("All elements must be of type Vec3")
95
- self._data.append(v)
129
+ vec_list.append([v.x, v.y, v.z])
130
+
131
+ new_rows = np.array(vec_list, dtype=np.float64)
132
+ if len(self._data) == 0:
133
+ self._data = new_rows
134
+ else:
135
+ self._data = np.vstack([self._data, new_rows])
96
136
 
97
137
  def to_list(self):
98
138
  """
@@ -101,22 +141,35 @@ class Vec3Array:
101
141
  Returns:
102
142
  list: A list of x, y, z components concatenated.
103
143
  """
104
- return [comp for vec in self._data for comp in vec]
144
+ return self._data.flatten().tolist()
105
145
 
106
146
  def to_numpy(self):
107
147
  """
108
148
  Convert the array of Vec3 objects to a numpy array.
149
+ This is the primary method for GPU data transfer.
150
+
151
+ Returns:
152
+ numpy.ndarray: A float32 numpy array of shape (N*3,) for GPU transfer.
153
+ """
154
+ return self._data.astype(np.float32).flatten()
155
+
156
+ def get_array(self):
157
+ """
158
+ Get the underlying numpy array in shape (N, 3).
159
+ Useful for vectorized operations.
109
160
 
110
161
  Returns:
111
- numpy.ndarray: A numpy array of the vector data.
162
+ numpy.ndarray: The internal float64 array of shape (N, 3).
112
163
  """
113
- return np.array(self.to_list(), dtype=np.float32)
164
+ return self._data
114
165
 
115
166
  def __repr__(self):
116
- return f"Vec3Array({self._data!r})"
167
+ vec_list = [Vec3(row[0], row[1], row[2]) for row in self._data]
168
+ return f"Vec3Array({vec_list!r})"
117
169
 
118
170
  def __str__(self):
119
- return str(self._data)
171
+ vec_list = [Vec3(row[0], row[1], row[2]) for row in self._data]
172
+ return str(vec_list)
120
173
 
121
174
  def sizeof(self):
122
175
  """