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 +11 -1
- ncca/ngl/abstract_vao.py +6 -2
- ncca/ngl/prim_data.py +603 -0
- ncca/ngl/primitives.py +38 -525
- ncca/ngl/quaternion.py +6 -2
- ncca/ngl/shader_program.py +165 -23
- ncca/ngl/text.py +9 -3
- ncca/ngl/util.py +46 -18
- ncca/ngl/vec2.py +3 -1
- ncca/ngl/vec3.py +23 -9
- ncca/ngl/vec4.py +16 -4
- {ncca_ngl-0.1.4.dist-info → ncca_ngl-0.2.1.dist-info}/METADATA +1 -1
- {ncca_ngl-0.1.4.dist-info → ncca_ngl-0.2.1.dist-info}/RECORD +14 -13
- {ncca_ngl-0.1.4.dist-info → ncca_ngl-0.2.1.dist-info}/WHEEL +1 -1
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 .
|
|
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(
|
|
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(
|
|
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")
|