ncca-ngl 0.3.5__py3-none-any.whl → 0.5.1__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 +5 -5
  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 +50 -0
  16. ncca/ngl/vec2.py +59 -302
  17. ncca/ngl/vec2_array.py +79 -28
  18. ncca/ngl/vec3.py +60 -350
  19. ncca/ngl/vec3_array.py +76 -23
  20. ncca/ngl/vec4.py +90 -200
  21. ncca/ngl/vec4_array.py +78 -27
  22. ncca/ngl/vector_base.py +548 -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 +33 -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.5.dist-info → ncca_ngl-0.5.1.dist-info}/METADATA +3 -2
  53. ncca_ngl-0.5.1.dist-info/RECORD +105 -0
  54. ncca/ngl/widgets/transformation_widget.py +0 -299
  55. ncca_ngl-0.3.5.dist-info/RECORD +0 -82
  56. {ncca_ngl-0.3.5.dist-info → ncca_ngl-0.5.1.dist-info}/WHEEL +0 -0
ncca/ngl/vec3.py CHANGED
@@ -1,410 +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
- from .util import clamp, hash_combine
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 __hash__(self):
43
- # Use 32-bit float element hashes, then combine
44
- seed = 0
45
- for v in (self.x, self.y, self.z):
46
- # ensure 32-bit float semantics
47
- h = hash(float(np.float32(v)))
48
- seed = hash_combine(seed, h)
49
- return seed
50
-
51
- def __iter__(self):
52
- """
53
- Make the Vec3 class iterable.
54
- Yields:
55
- float: The x, y, and z components of the vector.
56
- """
57
- yield self.x
58
- yield self.y
59
- yield self.z
60
-
61
- def __getitem__(self, index):
62
- """
63
- Get the component of the vector at the given index.
64
- Args:
65
- index (int): The index of the component (0 for x, 1 for y, 2 for z).
66
- Returns:
67
- float: The value of the component at the given index.
68
- Raises:
69
- IndexError: If the index is out of range.
70
- """
71
- components = [self.x, self.y, self.z]
72
- try:
73
- return components[index]
74
- except IndexError:
75
- raise IndexError("Index out of range. Valid indices are 0, 1, and 2.")
76
-
77
- def _validate_and_set(self, v, name):
78
- """
79
- check if v is a float or int
80
- Args:
81
- v (number): The value to check.
82
- Raises:
83
- ValueError: If v is not a float or int.
84
- """
85
- if not isinstance(v, (int, float, np.float32)):
86
- raise ValueError("need float or int")
87
- else:
88
- setattr(self, name, v)
89
-
90
- def copy(self) -> "Vec3":
91
- """
92
- Create a copy of the current vector.
93
- Returns:
94
- Vec3: A new instance of Vec3 with the same x, y, and z values.
95
- """
96
- return Vec3(self.x, self.y, self.z)
97
-
98
- def __add__(self, rhs):
99
- """
100
- vector addition a+b
101
-
102
- Args:
103
- rhs (Vec3): The right-hand side vector to add.
104
- Returns:
105
- Vec3: A new vector that is the result of adding this vector and the rhs vector.
106
- """
107
- r = Vec3()
108
- r.x = self.x + rhs.x
109
- r.y = self.y + rhs.y
110
- r.z = self.z + rhs.z
111
- return r
112
-
113
- def __iadd__(self, rhs):
114
- """
115
- vector addition a+=b
25
+ DIMENSION = 3
26
+ COMPONENT_NAMES = ("x", "y", "z")
27
+ DEFAULT_VALUES = (0.0, 0.0, 0.0)
116
28
 
117
- Args:
118
- rhs (Vec3): The right-hand side vector to add.
119
- Returns:
120
- Vec3: returns this vector after adding the rhs vector.
121
- """
122
- self.x += rhs.x
123
- self.y += rhs.y
124
- self.z += rhs.z
125
- return self
29
+ __slots__ = ["_data"]
126
30
 
127
- def __sub__(self, rhs):
31
+ def cross(self, rhs: "Vec3") -> "Vec3":
128
32
  """
129
- vector subtraction a-b
33
+ Cross product of two vectors a x b.
130
34
 
131
- Args:
132
- rhs (Vec3): The right-hand side vector to subtract.
133
- Returns:
134
- Vec3: A new vector that is the result of subtracting this vector and the rhs vector.
135
- """
136
- r = Vec3()
137
- r.x = self.x - rhs.x
138
- r.y = self.y - rhs.y
139
- r.z = self.z - rhs.z
140
- return r
141
-
142
- def __isub__(self, rhs):
143
- """
144
- vector subtraction a-=b
145
-
146
- Args:
147
- rhs (Vec3): The right-hand side vector to add.
148
- Returns:
149
- Vec3: returns this vector after subtracting the rhs vector.
150
- """
151
- self.x -= rhs.x
152
- self.y -= rhs.y
153
- self.z -= rhs.z
154
- return self
155
-
156
- def __eq__(self, rhs):
157
- """
158
- vector comparison a==b using math.isclose not we only compare to 6 decimal places
159
- Args:
160
- rhs (Vec3): The right-hand side vector to compare.
161
- Returns:
162
- bool: True if the vectors are close, False otherwise.
163
- NotImplemented: If the right-hand side is not a Vec3.
164
- """
165
-
166
- if not isinstance(rhs, Vec3):
167
- return NotImplemented
168
- return math.isclose(self.x, rhs.x) and math.isclose(self.y, rhs.y) and math.isclose(self.z, rhs.z)
169
-
170
- def __neq__(self, rhs):
171
- """
172
- vector comparison a!=b using math.isclose not we only compare to 6 decimal places
173
- Args:
174
- rhs (Vec3): The right-hand side vector to compare.
175
- Returns:
176
- bool: True if the vectors are not close, False otherwise.
177
- NotImplemented: If the right-hand side is not a Vec3.
178
- """
179
- if not isinstance(rhs, Vec3):
180
- return NotImplemented
181
- return not (math.isclose(self.x, rhs.x) and math.isclose(self.y, rhs.y) and math.isclose(self.z, rhs.z))
182
-
183
- def __neg__(self):
184
- """
185
- negate a vector -a
186
- """
187
- self.x = -self.x
188
- self.y = -self.y
189
- self.z = -self.z
190
- return self
191
-
192
- def set(self, x, y, z):
193
- """
194
- set the x,y,z values of the vector
195
- Args:
196
- x (float): The x-coordinate of the vector.
197
- y (float): The y-coordinate of the vector.
198
- z (float): The z-coordinate of the vector.
199
- Raises :
200
- ValueError: if x,y,z are not float
201
- """
202
- try:
203
- self.x = float(x)
204
- self.y = float(y)
205
- self.z = float(z)
206
- except ValueError:
207
- raise ValueError(f"Vec3.set {x=} {y=} {z=} all need to be float")
208
-
209
- def dot(self, rhs):
210
- """
211
- dot product of two vectors a.b
212
- Args:
213
- rhs (Vec3): The right-hand side vector to dot product with.
214
- """
215
- return self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
216
-
217
- def length(self):
218
- """
219
- length of vector
220
- Returns:
221
- float: The length of the vector.
222
- """
223
- return math.sqrt(self.x**2 + self.y**2 + self.z**2)
224
-
225
- def length_squared(self):
226
- """
227
- length of vector squared sometimes used to avoid the sqrt for performance
228
- Returns:
229
- float: The length of the vector squared
230
- """
231
- return self.x**2 + self.y**2 + self.z**2
232
-
233
- def inner(self, rhs):
234
- """
235
- inner product of two vectors a.b
236
- Args:
237
- rhs (Vec3): The right-hand side vector to inner product with.
238
- Returns:
239
- float: The inner product of the two vectors.
240
- """
241
- return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z)
242
-
243
- def null(self):
244
- """
245
- set the vector to zero
246
- """
247
- self.x = 0.0
248
- self.y = 0.0
249
- self.z = 0.0
250
-
251
- def cross(self, rhs):
252
- """
253
- cross product of two vectors a x b
254
35
  Args:
255
36
  rhs (Vec3): The right-hand side vector to cross product with.
37
+
256
38
  Returns:
257
39
  Vec3: A new vector that is the result of the cross product.
258
40
  """
259
- return Vec3(
260
- self.y * rhs.z - self.z * rhs.y,
261
- self.z * rhs.x - self.x * rhs.z,
262
- self.x * rhs.y - self.y * rhs.x,
263
- )
41
+ result = Vec3()
42
+ result._data = np.cross(self._data, rhs._data)
43
+ return result
264
44
 
265
- def normalize(self):
45
+ def reflect(self, n: "Vec3") -> "Vec3":
266
46
  """
267
- normalize the vector to unit length
268
- Returns:
269
- Vec3: A new vector that is the result of normalizing this vector.
270
- Raises:
271
- ZeroDivisionError: If the length of the vector is zero.
272
- """
273
- vector_length = self.length()
274
- try:
275
- self.x /= vector_length
276
- self.y /= vector_length
277
- self.z /= vector_length
278
- except ZeroDivisionError:
279
- raise ZeroDivisionError(
280
- f"Vec3.normalize {vector_length} length is zero most likely calling normalize on a zero vector"
281
- )
282
- return self
47
+ Reflect a vector about a normal.
283
48
 
284
- def reflect(self, n):
285
- """
286
- reflect a vector about a normal
287
49
  Args:
288
50
  n (Vec3): The normal to reflect about.
51
+
289
52
  Returns:
290
53
  Vec3: A new vector that is the result of reflecting this vector about the normal.
291
54
  """
292
55
  d = self.dot(n)
293
- # I - 2.0 * dot(N, I) * N
294
- 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
295
60
 
296
- def clamp(self, low, high):
61
+ def outer(self, rhs: "Vec3"):
297
62
  """
298
- clamp the vector to a range
299
- Args:
300
- low (float): The low end of the range.
301
- high (float): The high end of the range.
63
+ Outer product of two vectors a x b.
302
64
 
303
- """
304
- self.x = clamp(self.x, low, high)
305
- self.y = clamp(self.y, low, high)
306
- self.z = clamp(self.z, low, high)
307
-
308
- def __repr__(self):
309
- "object representation for debugging"
310
- return f"Vec3 [{self.x},{self.y},{self.z}]"
311
-
312
- def __str__(self):
313
- "object representation for debugging"
314
- return f"[{self.x},{self.y},{self.z}]"
315
-
316
- def outer(self, rhs):
317
- """
318
- outer product of two vectors a x b
319
65
  Args:
320
66
  rhs (Vec3): The right-hand side vector to outer product with.
67
+
321
68
  Returns:
322
69
  Mat3: A new 3x3 matrix that is the result of the outer product.
323
70
  """
324
71
  from .mat3 import Mat3
325
72
 
326
- return Mat3.from_list([
327
- [self.x * rhs.x, self.x * rhs.y, self.x * rhs.z],
328
- [self.y * rhs.x, self.y * rhs.y, self.y * rhs.z],
329
- [self.z * rhs.x, self.z * rhs.y, self.z * rhs.z],
330
- ])
331
-
332
- def __mul__(self, rhs):
333
- """
334
- piecewise scalar multiplication
335
- Args:
336
- rhs (float): The scalar to multiply by.
337
- Returns:
338
- Vec3: A new vector that is the result of multiplying this vector by the scalar.
339
- Raises:
340
- ValueError: If the right-hand side is not a float.
341
- """
342
- if isinstance(rhs, (float, int)):
343
- return Vec3(self.x * rhs, self.y * rhs, self.z * rhs)
344
- else:
345
- raise ValueError(f"can only do piecewise multiplication with a scalar {rhs=}")
346
-
347
- def __rmul__(self, rhs):
348
- """
349
- piecewise scalar multiplication
350
- Args:
351
- rhs (float): The scalar to multiply by.
352
- Returns:
353
- Vec3: A new vector that is the result of multiplying this vector by the scalar.
354
- Raises:
355
- ValueError: If the right-hand side is not a float.
356
- """
357
- return self * rhs
358
-
359
- def __truediv__(self, rhs):
360
- """
361
- piecewise scalar division
362
- Args:
363
- rhs (float): The scalar to divide by.
364
- Returns:
365
- Vec3: A new vector that is the result of dividing this vector by the scalar.
366
- Raises:
367
- ValueError: If the right-hand side is not a float.
368
- """
369
- if isinstance(rhs, (float, int)):
370
- return Vec3(self.x / rhs, self.y / rhs, self.z / rhs)
371
- elif isinstance(rhs, Vec3):
372
- return Vec3(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z)
373
- else:
374
- 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
375
76
 
376
77
  def __matmul__(self, rhs):
377
78
  """
378
- "Vec3 @ Mat3 matrix multiplication"
79
+ Vec3 @ Mat3 matrix multiplication.
80
+
379
81
  Args:
380
82
  rhs (Mat3): The matrix to multiply by.
83
+
381
84
  Returns:
382
85
  Vec3: A new vector that is the result of multiplying this vector by the matrix.
383
86
  """
384
- return Vec3(
385
- self.x * rhs.m[0][0] + self.y * rhs.m[1][0] + self.z * rhs.m[2][0],
386
- self.x * rhs.m[0][1] + self.y * rhs.m[1][1] + self.z * rhs.m[2][1],
387
- self.x * rhs.m[0][2] + self.y * rhs.m[1][2] + self.z * rhs.m[2][2],
388
- )
389
-
390
- def to_list(self):
391
- return [self.x, self.y, self.z]
87
+ result = Vec3()
88
+ result._data = rhs.m.T @ self._data # More efficient
89
+ return result
392
90
 
393
- def to_numpy(self):
394
- 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.
395
94
 
95
+ Args:
96
+ *args: Component values (x, y, z).
396
97
 
397
- # Helper function to create properties
398
- def _create_property(attr_name):
399
- def getter(self):
400
- 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")
401
109
 
402
- def setter(self, value):
403
- 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]}]"
404
113
 
405
- 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]}]"
406
117
 
407
118
 
408
- # Dynamically add properties for x, y, z
409
- for attr in ["x", "y", "z"]:
410
- 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
  """