ncca-ngl 0.1.1__py3-none-any.whl → 0.1.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.
Files changed (52) hide show
  1. ncca/ngl/PrimData/Primitives.npz +0 -0
  2. ncca/ngl/PrimData/pack_arrays.py +20 -0
  3. ncca/ngl/__init__.py +100 -0
  4. ncca/ngl/abstract_vao.py +85 -0
  5. ncca/ngl/base_mesh.py +170 -0
  6. ncca/ngl/base_mesh.pyi +11 -0
  7. ncca/ngl/bbox.py +224 -0
  8. ncca/ngl/bezier_curve.py +75 -0
  9. ncca/ngl/first_person_camera.py +174 -0
  10. ncca/ngl/image.py +94 -0
  11. ncca/ngl/log.py +44 -0
  12. ncca/ngl/mat2.py +128 -0
  13. ncca/ngl/mat3.py +466 -0
  14. ncca/ngl/mat4.py +456 -0
  15. ncca/ngl/multi_buffer_vao.py +49 -0
  16. ncca/ngl/obj.py +416 -0
  17. ncca/ngl/plane.py +47 -0
  18. ncca/ngl/primitives.py +706 -0
  19. ncca/ngl/pyside_event_handling_mixin.py +318 -0
  20. ncca/ngl/quaternion.py +112 -0
  21. ncca/ngl/random.py +167 -0
  22. ncca/ngl/shader.py +229 -0
  23. ncca/ngl/shader_lib.py +536 -0
  24. ncca/ngl/shader_program.py +816 -0
  25. ncca/ngl/shaders/checker_fragment.glsl +35 -0
  26. ncca/ngl/shaders/checker_vertex.glsl +19 -0
  27. ncca/ngl/shaders/colour_fragment.glsl +8 -0
  28. ncca/ngl/shaders/colour_vertex.glsl +11 -0
  29. ncca/ngl/shaders/diffuse_fragment.glsl +21 -0
  30. ncca/ngl/shaders/diffuse_vertex.glsl +24 -0
  31. ncca/ngl/shaders/text_fragment.glsl +10 -0
  32. ncca/ngl/shaders/text_geometry.glsl +53 -0
  33. ncca/ngl/shaders/text_vertex.glsl +18 -0
  34. ncca/ngl/simple_index_vao.py +65 -0
  35. ncca/ngl/simple_vao.py +42 -0
  36. ncca/ngl/text.py +346 -0
  37. ncca/ngl/texture.py +75 -0
  38. ncca/ngl/transform.py +95 -0
  39. ncca/ngl/util.py +128 -0
  40. ncca/ngl/vao_factory.py +34 -0
  41. ncca/ngl/vec2.py +350 -0
  42. ncca/ngl/vec2_array.py +106 -0
  43. ncca/ngl/vec3.py +401 -0
  44. ncca/ngl/vec3_array.py +110 -0
  45. ncca/ngl/vec4.py +229 -0
  46. ncca/ngl/vec4_array.py +106 -0
  47. {ncca_ngl-0.1.1.dist-info → ncca_ngl-0.1.2.dist-info}/METADATA +10 -9
  48. ncca_ngl-0.1.2.dist-info/RECORD +51 -0
  49. {ncca_ngl-0.1.1.dist-info → ncca_ngl-0.1.2.dist-info}/WHEEL +2 -1
  50. ncca_ngl-0.1.2.dist-info/top_level.txt +1 -0
  51. ncca_ngl-0.1.1.dist-info/RECORD +0 -4
  52. {ncca_ngl-0.1.1.dist-info → ncca_ngl-0.1.2.dist-info}/licenses/LICENSE.txt +0 -0
ncca/ngl/primitives.py ADDED
@@ -0,0 +1,706 @@
1
+ """
2
+ OpenGL primitive generation and drawing functions
3
+ In this class we can generate a pipeline for drawing our data for the most part it will be
4
+ x,y,z nx,ny,nz and u,v data in a flat numpy array.
5
+ We need to create the data first which is stored in a map as part of the class, we can then call draw
6
+ which will generate a pipeline for this object and draw into the current context.
7
+ """
8
+
9
+ import enum
10
+ from pathlib import Path
11
+ from typing import Dict, Union
12
+
13
+ import numpy as np
14
+ import OpenGL.GL as gl
15
+
16
+ from .log import logger
17
+ from .simple_vao import VertexData
18
+ from .vao_factory import VAOFactory, VAOType # noqa
19
+ from .vec3 import Vec3
20
+
21
+
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
+ class _primitive:
70
+ """A private class to hold VAO data for a primitive."""
71
+
72
+ def __init__(self, prim_data: np.ndarray):
73
+ """
74
+ Initializes the primitive with the given data.
75
+
76
+ Args:
77
+ prim_data: A numpy array containing the vertex data (x,y,z,nx,ny,nz,u,v).
78
+ """
79
+ self.vao = VAOFactory.create_vao(VAOType.SIMPLE, gl.GL_TRIANGLES)
80
+ with self.vao:
81
+ data = VertexData(data=prim_data.data, size=prim_data.size)
82
+ self.vao.set_data(data)
83
+ vert_data_size = 8 * 4 # 4 is sizeof float and 8 is x,y,z,nx,ny,nz,uv
84
+ 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
+ )
91
+ self.vao.set_num_indices(prim_data.size // 8)
92
+
93
+
94
+ class Primitives:
95
+ """A static class for creating and drawing primitives."""
96
+
97
+ # this is effectively a static class so we can use it to store data
98
+ # and generate pipelines for drawing
99
+ _primitives: Dict[str, _primitive] = {}
100
+ _loaded: bool = False
101
+
102
+ @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:
119
+ """
120
+ Creates a line grid primitive.
121
+
122
+ 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
154
+
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
159
+
160
+ @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
217
+
218
+ @classmethod
219
+ def draw(cls, name: Union[str, Prims]) -> None:
220
+ """
221
+ Draws the specified primitive.
222
+
223
+ Args:
224
+ name: The name of the primitive to draw, either as a string or a Prims enum.
225
+ """
226
+ key = name.value if isinstance(name, Prims) else name
227
+ try:
228
+ prim = cls._primitives[key]
229
+ with prim.vao:
230
+ prim.vao.draw()
231
+ except KeyError:
232
+ logger.error(f"Failed to draw primitive {key}")
233
+ 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