ncca-ngl 0.1.4__py3-none-any.whl → 0.2.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.
ncca/ngl/__init__.py CHANGED
@@ -1,3 +1,11 @@
1
+ # generate auto __version__
2
+ from importlib.metadata import PackageNotFoundError, version
3
+
4
+ try:
5
+ __version__ = version("ncca-ngl")
6
+ except PackageNotFoundError:
7
+ __version__ = "0.0.0"
8
+
1
9
  from .abstract_vao import AbstractVAO, VertexData
2
10
  from .base_mesh import BaseMesh, Face
3
11
  from .bbox import BBox
@@ -17,7 +25,8 @@ from .obj import (
17
25
  ObjParseVertexError,
18
26
  )
19
27
  from .plane import Plane
20
- from .primitives import Primitives, Prims
28
+ from .prim_data import PrimData, Prims
29
+ from .primitives import Primitives
21
30
  from .pyside_event_handling_mixin import PySideEventHandlingMixin
22
31
  from .quaternion import Quaternion
23
32
  from .random import Random
@@ -95,6 +104,7 @@ all = [
95
104
  logger,
96
105
  Primitives,
97
106
  Prims,
107
+ PrimData,
98
108
  FirstPersonCamera,
99
109
  PySideEventHandlingMixin,
100
110
  ]
ncca/ngl/abstract_vao.py CHANGED
@@ -52,10 +52,14 @@ class AbstractVAO(abc.ABC):
52
52
  def remove_vao(self):
53
53
  raise NotImplementedError
54
54
 
55
- def set_vertex_attribute_pointer(self, id, size, type, stride, offset, normalize=False):
55
+ def set_vertex_attribute_pointer(
56
+ self, id, size, type, stride, offset, normalize=False
57
+ ):
56
58
  if not self.bound:
57
59
  logger.error("VAO not bound in set_vertex_attribute_pointer")
58
- gl.glVertexAttribPointer(id, size, type, normalize, stride, ctypes.c_void_p(offset))
60
+ gl.glVertexAttribPointer(
61
+ id, size, type, normalize, stride, ctypes.c_void_p(offset)
62
+ )
59
63
  gl.glEnableVertexAttribArray(id)
60
64
 
61
65
  def set_num_indices(self, count):
ncca/ngl/prim_data.py ADDED
@@ -0,0 +1,603 @@
1
+ import enum
2
+ from pathlib import Path
3
+ from typing import Union
4
+
5
+ import numpy as np
6
+
7
+ from .vec3 import Vec3
8
+
9
+
10
+ class Prims(enum.Enum):
11
+ """Enum for the default primitives that can be loaded."""
12
+
13
+ BUDDAH = "buddah"
14
+ BUNNY = "bunny"
15
+ CUBE = "cube"
16
+ DODECAHEDRON = "dodecahedron"
17
+ DRAGON = "dragon"
18
+ FOOTBALL = "football"
19
+ ICOSAHEDRON = "icosahedron"
20
+ OCTAHEDRON = "octahedron"
21
+ TEAPOT = "teapot"
22
+ TETRAHEDRON = "tetrahedron"
23
+ TROLL = "troll"
24
+
25
+
26
+ def _circle_table(n: int) -> np.ndarray:
27
+ """
28
+ Generates a table of sine and cosine values for a circle divided into n segments.
29
+
30
+ Args:
31
+ n: The number of segments to divide the circle into.
32
+
33
+ Returns:
34
+ A numpy array of shape (n+1, 2) containing the cosine and sine values.
35
+ """
36
+ # Determine the angle between samples
37
+ angle = 2.0 * np.pi / (n if n != 0 else 1)
38
+
39
+ # Allocate list for n samples, plus duplicate of first entry at the end
40
+ cs = np.zeros((n + 1, 2), dtype=np.float32)
41
+
42
+ # Compute cos and sin around the circle
43
+ cs[0, 0] = 1.0 # cost
44
+ cs[0, 1] = 0.0 # sint
45
+
46
+ for i in range(1, n):
47
+ cs[i, 1] = np.sin(angle * i) # sint
48
+ cs[i, 0] = np.cos(angle * i) # cost
49
+
50
+ # Last sample is duplicate of the first
51
+ cs[n, 1] = cs[0, 1] # sint
52
+ cs[n, 0] = cs[0, 0] # cost
53
+
54
+ return cs
55
+
56
+
57
+ class PrimData:
58
+ @staticmethod
59
+ def line_grid(width: float, depth: float, steps: int) -> np.ndarray:
60
+ """
61
+ Creates a line grid primitive.
62
+
63
+ Args:
64
+ width: The width of the grid.
65
+ depth: The depth of the grid.
66
+ steps: The number of steps in the grid.
67
+ """
68
+ # Calculate the step size for each grid value
69
+ wstep = width / steps
70
+ ws2 = width / 2.0
71
+ v1 = -ws2
72
+
73
+ dstep = depth / steps
74
+ ds2 = depth / 2.0
75
+ v2 = -ds2
76
+
77
+ # Create a list to store the vertex data
78
+ data = []
79
+
80
+ for _ in range(steps + 1):
81
+ # Vertex 1 x, y, z
82
+ data.append([-ws2, 0.0, v1])
83
+ # Vertex 2 x, y, z
84
+ data.append([ws2, 0.0, v1])
85
+
86
+ # Vertex 1 x, y, z
87
+ data.append([v2, 0.0, ds2])
88
+ # Vertex 2 x, y, z
89
+ data.append([v2, 0.0, -ds2])
90
+
91
+ # Now change our step value
92
+ v1 += wstep
93
+ v2 += dstep
94
+
95
+ # Convert the list to a NumPy array
96
+ return np.array(data, dtype=np.float32)
97
+
98
+ @staticmethod
99
+ def triangle_plane(width: float, depth: float, w_p: int, d_p: int, v_n: Vec3) -> np.ndarray:
100
+ """
101
+ Creates a triangle plane primitive.
102
+
103
+ Args:
104
+ width: The width of the plane.
105
+ depth: The depth of the plane.
106
+ w_p: The number of width partitions.
107
+ d_p: The number of depth partitions.
108
+ v_n: The normal vector for the plane.
109
+ """
110
+ w2 = width / 2.0
111
+ d2 = depth / 2.0
112
+ w_step = width / w_p
113
+ d_step = depth / d_p
114
+
115
+ du = 0.9 / w_p
116
+ dv = 0.9 / d_p
117
+
118
+ data = []
119
+ v = 0.0
120
+ d = -d2
121
+ for _ in range(d_p):
122
+ u = 0.0
123
+ w = -w2
124
+ for _ in range(w_p):
125
+ # tri 1
126
+ # vert 1
127
+ data.extend([w, 0.0, d + d_step, v_n.x, v_n.y, v_n.z, u, v + dv])
128
+ # vert 2
129
+ data.extend([w + w_step, 0.0, d + d_step, v_n.x, v_n.y, v_n.z, u + du, v + dv])
130
+ # vert 3
131
+ data.extend([w, 0.0, d, v_n.x, v_n.y, v_n.z, u, v])
132
+
133
+ # tri 2
134
+ # vert 1
135
+ data.extend([w + w_step, 0.0, d + d_step, v_n.x, v_n.y, v_n.z, u + du, v + dv])
136
+ # vert 2
137
+ data.extend([w + w_step, 0.0, d, v_n.x, v_n.y, v_n.z, u + du, v])
138
+ # vert 3
139
+ data.extend([w, 0.0, d, v_n.x, v_n.y, v_n.z, u, v])
140
+ u += du
141
+ w += w_step
142
+ v += dv
143
+ d += d_step
144
+
145
+ return np.array(data, dtype=np.float32)
146
+
147
+ @staticmethod
148
+ def sphere(radius: float, precision: int) -> np.ndarray:
149
+ """
150
+ Creates a sphere primitive.
151
+
152
+ Args:
153
+ radius: The radius of the sphere.
154
+ precision: The precision of the sphere (number of slices).
155
+ """
156
+ # Sphere code based on a function Written by Paul Bourke.
157
+ # http://astronomy.swin.edu.au/~pbourke/opengl/sphere/
158
+ # the next part of the code calculates the P,N,UV of the sphere for triangles
159
+
160
+ # Disallow a negative number for radius.
161
+ if radius < 0.0:
162
+ radius = -radius
163
+
164
+ # Disallow a negative number for precision.
165
+ if precision < 4:
166
+ precision = 4
167
+
168
+ # Create a numpy array to store our verts
169
+ data = []
170
+
171
+ for i in range(precision // 2):
172
+ theta1 = i * 2.0 * np.pi / precision - np.pi / 2.0
173
+ theta2 = (i + 1) * 2.0 * np.pi / precision - np.pi / 2.0
174
+
175
+ for j in range(precision):
176
+ theta3 = j * 2.0 * np.pi / precision
177
+ theta4 = (j + 1) * 2.0 * np.pi / precision
178
+
179
+ # First triangle
180
+ nx1 = np.cos(theta2) * np.cos(theta3)
181
+ ny1 = np.sin(theta2)
182
+ nz1 = np.cos(theta2) * np.sin(theta3)
183
+ x1 = radius * nx1
184
+ y1 = radius * ny1
185
+ z1 = radius * nz1
186
+ u1 = j / precision
187
+ v1 = 2.0 * (i + 1) / precision
188
+ data.append([x1, y1, z1, nx1, ny1, nz1, u1, v1])
189
+
190
+ nx2 = np.cos(theta1) * np.cos(theta3)
191
+ ny2 = np.sin(theta1)
192
+ nz2 = np.cos(theta1) * np.sin(theta3)
193
+ x2 = radius * nx2
194
+ y2 = radius * ny2
195
+ z2 = radius * nz2
196
+ u2 = j / precision
197
+ v2 = 2.0 * i / precision
198
+ data.append([x2, y2, z2, nx2, ny2, nz2, u2, v2])
199
+
200
+ nx3 = np.cos(theta1) * np.cos(theta4)
201
+ ny3 = np.sin(theta1)
202
+ nz3 = np.cos(theta1) * np.sin(theta4)
203
+ x3 = radius * nx3
204
+ y3 = radius * ny3
205
+ z3 = radius * nz3
206
+ u3 = (j + 1) / precision
207
+ v3 = 2.0 * i / precision
208
+ data.append([x3, y3, z3, nx3, ny3, nz3, u3, v3])
209
+
210
+ # Second triangle
211
+ nx4 = np.cos(theta2) * np.cos(theta4)
212
+ ny4 = np.sin(theta2)
213
+ nz4 = np.cos(theta2) * np.sin(theta4)
214
+ x4 = radius * nx4
215
+ y4 = radius * ny4
216
+ z4 = radius * nz4
217
+ u4 = (j + 1) / precision
218
+ v4 = 2.0 * (i + 1) / precision
219
+ data.append([x4, y4, z4, nx4, ny4, nz4, u4, v4])
220
+
221
+ data.append([x1, y1, z1, nx1, ny1, nz1, u1, v1])
222
+ data.append([x3, y3, z3, nx3, ny3, nz3, u3, v3])
223
+
224
+ return np.array(data, dtype=np.float32)
225
+
226
+ @staticmethod
227
+ def cone(base: float, height: float, slices: int, stacks: int) -> np.ndarray:
228
+ """
229
+ Creates a cone primitive.
230
+
231
+ Args:
232
+ base: The radius of the cone's base.
233
+ height: The height of the cone.
234
+ slices: The number of divisions around the cone.
235
+ stacks: The number of divisions along the cone's height.
236
+ """
237
+ z_step = height / (stacks if stacks > 0 else 1)
238
+ r_step = base / (stacks if stacks > 0 else 1)
239
+
240
+ cosn = height / np.sqrt(height * height + base * base)
241
+ sinn = base / np.sqrt(height * height + base * base)
242
+
243
+ cs = _circle_table(slices)
244
+
245
+ z0 = 0.0
246
+ z1 = z_step
247
+
248
+ r0 = base
249
+ r1 = r0 - r_step
250
+
251
+ du = 1.0 / stacks
252
+ dv = 1.0 / slices
253
+
254
+ u = 1.0
255
+ v = 1.0
256
+
257
+ data = []
258
+
259
+ for _ in range(stacks):
260
+ for j in range(slices):
261
+ # First triangle
262
+ d1 = [0] * 8
263
+ d1[6] = u
264
+ d1[7] = v
265
+ d1[3] = cs[j, 0] * cosn # nx
266
+ d1[4] = cs[j, 1] * sinn # ny
267
+ d1[5] = sinn # nz
268
+ d1[0] = cs[j, 0] * r0 # x
269
+ d1[1] = cs[j, 1] * r0 # y
270
+ d1[2] = z0 # z
271
+ data.append(d1)
272
+
273
+ d2 = [0] * 8
274
+ d2[6] = u
275
+ d2[7] = v - dv
276
+ d2[3] = cs[j, 0] * cosn # nx
277
+ d2[4] = cs[j, 1] * sinn # ny
278
+ d2[5] = sinn # nz
279
+ d2[0] = cs[j, 0] * r1 # x
280
+ d2[1] = cs[j, 1] * r1 # y
281
+ d2[2] = z1 # z
282
+ data.append(d2)
283
+
284
+ d3 = [0] * 8
285
+ d3[6] = u - du
286
+ d3[7] = v - dv
287
+ d3[3] = cs[j + 1, 0] * cosn # nx
288
+ d3[4] = cs[j + 1, 1] * sinn # ny
289
+ d3[5] = sinn # nz
290
+ d3[0] = cs[j + 1, 0] * r1 # x
291
+ d3[1] = cs[j + 1, 1] * r1 # y
292
+ d3[2] = z1 # z
293
+ data.append(d3)
294
+
295
+ # Second triangle
296
+ d4 = [0] * 8
297
+ d4[6] = u
298
+ d4[7] = v
299
+ d4[3] = cs[j, 0] * cosn # nx
300
+ d4[4] = cs[j, 1] * sinn # ny
301
+ d4[5] = sinn # nz
302
+ d4[0] = cs[j, 0] * r0 # x
303
+ d4[1] = cs[j, 1] * r0 # y
304
+ d4[2] = z0 # z
305
+ data.append(d4)
306
+
307
+ d5 = [0] * 8
308
+ d5[6] = u - du
309
+ d5[7] = v - dv
310
+ d5[3] = cs[j + 1, 0] * cosn # nx
311
+ d5[4] = cs[j + 1, 1] * sinn # ny
312
+ d5[5] = sinn # nz
313
+ d5[0] = cs[j + 1, 0] * r1 # x
314
+ d5[1] = cs[j + 1, 1] * r1 # y
315
+ d5[2] = z1 # z
316
+ data.append(d5)
317
+
318
+ d6 = [0] * 8
319
+ d6[6] = u - du
320
+ d6[7] = v
321
+ d6[3] = cs[j + 1, 0] * cosn # nx
322
+ d6[4] = cs[j + 1, 1] * sinn # ny
323
+ d6[5] = sinn # nz
324
+ d6[0] = cs[j + 1, 0] * r0 # x
325
+ d6[1] = cs[j + 1, 1] * r0 # y
326
+ d6[2] = z0 # z
327
+ data.append(d6)
328
+
329
+ u -= du
330
+
331
+ v -= dv
332
+ u = 1.0
333
+ z0 = z1
334
+ z1 += z_step
335
+ r0 = r1
336
+ r1 -= r_step
337
+
338
+ return np.array(data, dtype=np.float32)
339
+
340
+ @staticmethod
341
+ def capsule(radius: float, height: float, precision: int) -> np.ndarray:
342
+ """
343
+ Creates a capsule primitive.
344
+ The capsule is aligned along the y-axis.
345
+ It is composed of a cylinder and two hemispherical caps.
346
+ based on code from here https://code.google.com/p/rgine/source/browse/trunk/RGine/opengl/src/RGLShapes.cpp
347
+ and adapted
348
+ """
349
+ if radius <= 0.0:
350
+ raise ValueError("Radius must be positive")
351
+ if height < 0.0:
352
+ raise ValueError("Height must be non-negative")
353
+ if precision < 4:
354
+ precision = 4
355
+
356
+ data = []
357
+ h = height / 2.0
358
+ ang = np.pi / precision
359
+
360
+ # Cylinder sides
361
+ for i in range(2 * precision):
362
+ c = radius * np.cos(ang * i)
363
+ c1 = radius * np.cos(ang * (i + 1))
364
+ s = radius * np.sin(ang * i)
365
+ s1 = radius * np.sin(ang * (i + 1))
366
+
367
+ # normals for cylinder sides
368
+ nc = np.cos(ang * i)
369
+ ns = np.sin(ang * i)
370
+ nc1 = np.cos(ang * (i + 1))
371
+ ns1 = np.sin(ang * (i + 1))
372
+
373
+ # side top
374
+ data.extend([c1, h, s1, nc1, 0.0, ns1, 0.0, 0.0])
375
+ data.extend([c, h, s, nc, 0.0, ns, 0.0, 0.0])
376
+ data.extend([c, -h, s, nc, 0.0, ns, 0.0, 0.0])
377
+
378
+ # side bot
379
+ data.extend([c, -h, s, nc, 0.0, ns, 0.0, 0.0])
380
+ data.extend([c1, -h, s1, nc1, 0.0, ns1, 0.0, 0.0])
381
+ data.extend([c1, h, s1, nc1, 0.0, ns1, 0.0, 0.0])
382
+ # Hemispherical caps
383
+ for i in range(2 * precision):
384
+ # longitude
385
+ s = -np.sin(ang * i)
386
+ s1 = -np.sin(ang * (i + 1))
387
+ c = np.cos(ang * i)
388
+ c1 = np.cos(ang * (i + 1))
389
+
390
+ for j in range(precision + 1):
391
+ o = h if j < precision / 2 else -h
392
+
393
+ # latitude
394
+ sb = radius * np.sin(ang * j)
395
+ sb1 = radius * np.sin(ang * (j + 1))
396
+ cb = radius * np.cos(ang * j)
397
+ cb1 = radius * np.cos(ang * (j + 1))
398
+
399
+ if j != precision - 1:
400
+ nx, ny, nz = sb * c, cb, sb * s
401
+ data.extend([nx, ny + o, nz, nx, ny, nz, 0.0, 0.0])
402
+ nx, ny, nz = sb1 * c, cb1, sb1 * s
403
+ data.extend([nx, ny + o, nz, nx, ny, nz, 0.0, 0.0])
404
+ nx, ny, nz = sb1 * c1, cb1, sb1 * s1
405
+ data.extend([nx, ny + o, nz, nx, ny, nz, 0.0, 0.0])
406
+
407
+ if j != 0:
408
+ nx, ny, nz = sb * c, cb, sb * s
409
+ data.extend([nx, ny + o, nz, nx, ny, nz, 0.0, 0.0])
410
+ nx, ny, nz = sb1 * c1, cb1, sb1 * s1
411
+ data.extend([nx, ny + o, nz, nx, ny, nz, 0.0, 0.0])
412
+ nx, ny, nz = sb * c1, cb, sb * s1
413
+ data.extend([nx, ny + o, nz, nx, ny, nz, 0.0, 0.0])
414
+
415
+ return np.array(data, dtype=np.float32)
416
+
417
+ @staticmethod
418
+ def cylinder(radius: float, height: float, slices: int, stacks: int) -> np.ndarray:
419
+ """
420
+ Creates a cylinder primitive.
421
+ The cylinder is aligned along the y-axis.
422
+ This method generates the cylinder walls, but not the top and bottom caps.
423
+ """
424
+ if radius <= 0.0:
425
+ raise ValueError("Radius must be positive")
426
+ if height < 0.0:
427
+ raise ValueError("Height must be non-negative")
428
+ if slices < 3:
429
+ slices = 3
430
+ if stacks < 1:
431
+ stacks = 1
432
+
433
+ data = []
434
+ h2 = height / 2.0
435
+ y_step = height / stacks
436
+
437
+ cs = _circle_table(slices)
438
+
439
+ du = 1.0 / slices
440
+ dv = 1.0 / stacks
441
+
442
+ for i in range(stacks):
443
+ y0 = -h2 + i * y_step
444
+ y1 = -h2 + (i + 1) * y_step
445
+ v = i * dv
446
+ for j in range(slices):
447
+ u = j * du
448
+
449
+ nx1, nz1 = cs[j, 0], cs[j, 1]
450
+ x1, z1 = radius * nx1, radius * nz1
451
+
452
+ nx2, nz2 = cs[j + 1, 0], cs[j + 1, 1]
453
+ x2, z2 = radius * nx2, radius * nz2
454
+
455
+ p_bl = [x1, y0, z1, nx1, 0, nz1, u, v]
456
+ p_br = [x2, y0, z2, nx2, 0, nz2, u + du, v]
457
+ p_tl = [x1, y1, z1, nx1, 0, nz1, u, v + dv]
458
+ p_tr = [x2, y1, z2, nx2, 0, nz2, u + du, v + dv]
459
+
460
+ # Triangle 1
461
+ data.extend(p_bl)
462
+ data.extend(p_tl)
463
+ data.extend(p_br)
464
+ # Triangle 2
465
+ data.extend(p_br)
466
+ data.extend(p_tl)
467
+ data.extend(p_tr)
468
+
469
+ return np.array(data, dtype=np.float32)
470
+
471
+ @staticmethod
472
+ def disk(radius: float, slices: int) -> np.ndarray:
473
+ """
474
+ Creates a disk primitive.
475
+
476
+ Args:
477
+ radius: The radius of the disk.
478
+ slices: The number of slices to divide the disk into.
479
+ """
480
+ if radius <= 0.0:
481
+ raise ValueError("Radius must be positive")
482
+ if slices < 3:
483
+ slices = 3
484
+
485
+ data = []
486
+ cs = _circle_table(slices)
487
+
488
+ center = [0, 0, 0, 0, 1, 0, 0.5, 0.5]
489
+
490
+ for i in range(slices):
491
+ p1 = [
492
+ radius * cs[i, 0],
493
+ 0,
494
+ radius * cs[i, 1],
495
+ 0,
496
+ 1,
497
+ 0,
498
+ cs[i, 0] * 0.5 + 0.5,
499
+ cs[i, 1] * 0.5 + 0.5,
500
+ ]
501
+ p2 = [
502
+ radius * cs[i + 1, 0],
503
+ 0,
504
+ radius * cs[i + 1, 1],
505
+ 0,
506
+ 1,
507
+ 0,
508
+ cs[i + 1, 0] * 0.5 + 0.5,
509
+ cs[i + 1, 1] * 0.5 + 0.5,
510
+ ]
511
+
512
+ data.extend(center)
513
+ data.extend(p2)
514
+ data.extend(p1)
515
+
516
+ return np.array(data, dtype=np.float32)
517
+
518
+ @staticmethod
519
+ def torus(
520
+ minor_radius: float,
521
+ major_radius: float,
522
+ sides: int,
523
+ rings: int,
524
+ ) -> np.ndarray:
525
+ """
526
+ Creates a torus primitive.
527
+
528
+ Args:
529
+ minor_radius: The minor radius of the torus.
530
+ major_radius: The major radius of the torus.
531
+ sides: The number of sides for each ring.
532
+ rings: The number of rings for the torus.
533
+ """
534
+ if minor_radius <= 0 or major_radius <= 0:
535
+ raise ValueError("Radii must be positive")
536
+ if sides < 3 or rings < 3:
537
+ raise ValueError("Sides and rings must be at least 3")
538
+
539
+ d_psi = 2.0 * np.pi / rings
540
+ d_phi = -2.0 * np.pi / sides
541
+
542
+ psi = 0.0
543
+
544
+ vertices = []
545
+ normals = []
546
+ uvs = []
547
+
548
+ for j in range(rings + 1):
549
+ c_psi = np.cos(psi)
550
+ s_psi = np.sin(psi)
551
+ phi = 0.0
552
+ for i in range(sides + 1):
553
+ c_phi = np.cos(phi)
554
+ s_phi = np.sin(phi)
555
+
556
+ x = c_psi * (major_radius + c_phi * minor_radius)
557
+ z = s_psi * (major_radius + c_phi * minor_radius)
558
+ y = s_phi * minor_radius
559
+ vertices.append([x, y, z])
560
+
561
+ nx = c_psi * c_phi
562
+ nz = s_psi * c_phi
563
+ ny = s_phi
564
+ normals.append([nx, ny, nz])
565
+
566
+ u = i / sides
567
+ v = j / rings
568
+ uvs.append([u, v])
569
+
570
+ phi += d_phi
571
+ psi += d_psi
572
+
573
+ data = []
574
+ for j in range(rings):
575
+ for i in range(sides):
576
+ idx1 = j * (sides + 1) + i
577
+ idx2 = j * (sides + 1) + (i + 1)
578
+ idx3 = (j + 1) * (sides + 1) + i
579
+ idx4 = (j + 1) * (sides + 1) + (i + 1)
580
+
581
+ p1 = vertices[idx1] + normals[idx1] + uvs[idx1]
582
+ p2 = vertices[idx2] + normals[idx2] + uvs[idx2]
583
+ p3 = vertices[idx3] + normals[idx3] + uvs[idx3]
584
+ p4 = vertices[idx4] + normals[idx4] + uvs[idx4]
585
+
586
+ data.extend(p1)
587
+ data.extend(p3)
588
+ data.extend(p2)
589
+
590
+ data.extend(p2)
591
+ data.extend(p3)
592
+ data.extend(p4)
593
+
594
+ return np.array(data, dtype=np.float32)
595
+
596
+ @staticmethod
597
+ def primitive(name: Union[str, enum]) -> np.ndarray:
598
+ prim_folder = Path(__file__).parent / "PrimData"
599
+ prims = np.load(prim_folder / "Primitives.npz")
600
+ try:
601
+ return prims[name]
602
+ except KeyError:
603
+ raise ValueError(f"Primitive '{name}' not found")