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