pyrender-maintained 1.0.0__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.
pyrender/primitive.py ADDED
@@ -0,0 +1,489 @@
1
+ """Primitives, conforming to the glTF 2.0 standards as specified in
2
+ https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-primitive
3
+
4
+ Author: Matthew Matl
5
+ """
6
+ import numpy as np
7
+
8
+ from OpenGL.GL import *
9
+
10
+ from .material import Material, MetallicRoughnessMaterial
11
+ from .constants import FLOAT_SZ, UINT_SZ, BufFlags, GLTF
12
+ from .utils import format_color_array
13
+
14
+
15
+ class Primitive(object):
16
+ """A primitive object which can be rendered.
17
+
18
+ Parameters
19
+ ----------
20
+ positions : (n, 3) float
21
+ XYZ vertex positions.
22
+ normals : (n, 3) float
23
+ Normalized XYZ vertex normals.
24
+ tangents : (n, 4) float
25
+ XYZW vertex tangents where the w component is a sign value
26
+ (either +1 or -1) indicating the handedness of the tangent basis.
27
+ texcoord_0 : (n, 2) float
28
+ The first set of UV texture coordinates.
29
+ texcoord_1 : (n, 2) float
30
+ The second set of UV texture coordinates.
31
+ color_0 : (n, 4) float
32
+ RGBA vertex colors.
33
+ joints_0 : (n, 4) float
34
+ Joint information.
35
+ weights_0 : (n, 4) float
36
+ Weight information for morphing.
37
+ indices : (m, 3) int
38
+ Face indices for triangle meshes or fans.
39
+ material : :class:`Material`
40
+ The material to apply to this primitive when rendering.
41
+ mode : int
42
+ The type of primitives to render, one of the following:
43
+
44
+ - ``0``: POINTS
45
+ - ``1``: LINES
46
+ - ``2``: LINE_LOOP
47
+ - ``3``: LINE_STRIP
48
+ - ``4``: TRIANGLES
49
+ - ``5``: TRIANGLES_STRIP
50
+ - ``6``: TRIANGLES_FAN
51
+ targets : (k,) int
52
+ Morph target indices.
53
+ poses : (x,4,4), float
54
+ Array of 4x4 transformation matrices for instancing this object.
55
+ """
56
+
57
+ def __init__(self,
58
+ positions,
59
+ normals=None,
60
+ tangents=None,
61
+ texcoord_0=None,
62
+ texcoord_1=None,
63
+ color_0=None,
64
+ joints_0=None,
65
+ weights_0=None,
66
+ indices=None,
67
+ material=None,
68
+ mode=None,
69
+ targets=None,
70
+ poses=None):
71
+
72
+ if mode is None:
73
+ mode = GLTF.TRIANGLES
74
+
75
+ self.positions = positions
76
+ self.normals = normals
77
+ self.tangents = tangents
78
+ self.texcoord_0 = texcoord_0
79
+ self.texcoord_1 = texcoord_1
80
+ self.color_0 = color_0
81
+ self.joints_0 = joints_0
82
+ self.weights_0 = weights_0
83
+ self.indices = indices
84
+ self.material = material
85
+ self.mode = mode
86
+ self.targets = targets
87
+ self.poses = poses
88
+
89
+ self._bounds = None
90
+ self._vaid = None
91
+ self._buffers = []
92
+ self._is_transparent = None
93
+ self._buf_flags = None
94
+
95
+ @property
96
+ def positions(self):
97
+ """(n,3) float : XYZ vertex positions.
98
+ """
99
+ return self._positions
100
+
101
+ @positions.setter
102
+ def positions(self, value):
103
+ value = np.asanyarray(value, dtype=np.float32)
104
+ self._positions = np.ascontiguousarray(value)
105
+ self._bounds = None
106
+
107
+ @property
108
+ def normals(self):
109
+ """(n,3) float : Normalized XYZ vertex normals.
110
+ """
111
+ return self._normals
112
+
113
+ @normals.setter
114
+ def normals(self, value):
115
+ if value is not None:
116
+ value = np.asanyarray(value, dtype=np.float32)
117
+ value = np.ascontiguousarray(value)
118
+ if value.shape != self.positions.shape:
119
+ raise ValueError('Incorrect normals shape')
120
+ self._normals = value
121
+
122
+ @property
123
+ def tangents(self):
124
+ """(n,4) float : XYZW vertex tangents.
125
+ """
126
+ return self._tangents
127
+
128
+ @tangents.setter
129
+ def tangents(self, value):
130
+ if value is not None:
131
+ value = np.asanyarray(value, dtype=np.float32)
132
+ value = np.ascontiguousarray(value)
133
+ if value.shape != (self.positions.shape[0], 4):
134
+ raise ValueError('Incorrect tangent shape')
135
+ self._tangents = value
136
+
137
+ @property
138
+ def texcoord_0(self):
139
+ """(n,2) float : The first set of UV texture coordinates.
140
+ """
141
+ return self._texcoord_0
142
+
143
+ @texcoord_0.setter
144
+ def texcoord_0(self, value):
145
+ if value is not None:
146
+ value = np.asanyarray(value, dtype=np.float32)
147
+ value = np.ascontiguousarray(value)
148
+ if (value.ndim != 2 or value.shape[0] != self.positions.shape[0] or
149
+ value.shape[1] < 2):
150
+ raise ValueError('Incorrect texture coordinate shape')
151
+ if value.shape[1] > 2:
152
+ value = value[:,:2]
153
+ self._texcoord_0 = value
154
+
155
+ @property
156
+ def texcoord_1(self):
157
+ """(n,2) float : The second set of UV texture coordinates.
158
+ """
159
+ return self._texcoord_1
160
+
161
+ @texcoord_1.setter
162
+ def texcoord_1(self, value):
163
+ if value is not None:
164
+ value = np.asanyarray(value, dtype=np.float32)
165
+ value = np.ascontiguousarray(value)
166
+ if (value.ndim != 2 or value.shape[0] != self.positions.shape[0] or
167
+ value.shape[1] != 2):
168
+ raise ValueError('Incorrect texture coordinate shape')
169
+ self._texcoord_1 = value
170
+
171
+ @property
172
+ def color_0(self):
173
+ """(n,4) float : RGBA vertex colors.
174
+ """
175
+ return self._color_0
176
+
177
+ @color_0.setter
178
+ def color_0(self, value):
179
+ if value is not None:
180
+ value = np.ascontiguousarray(
181
+ format_color_array(value, shape=(len(self.positions), 4))
182
+ )
183
+ self._is_transparent = None
184
+ self._color_0 = value
185
+
186
+ @property
187
+ def joints_0(self):
188
+ """(n,4) float : Joint information.
189
+ """
190
+ return self._joints_0
191
+
192
+ @joints_0.setter
193
+ def joints_0(self, value):
194
+ self._joints_0 = value
195
+
196
+ @property
197
+ def weights_0(self):
198
+ """(n,4) float : Weight information for morphing.
199
+ """
200
+ return self._weights_0
201
+
202
+ @weights_0.setter
203
+ def weights_0(self, value):
204
+ self._weights_0 = value
205
+
206
+ @property
207
+ def indices(self):
208
+ """(m,3) int : Face indices for triangle meshes or fans.
209
+ """
210
+ return self._indices
211
+
212
+ @indices.setter
213
+ def indices(self, value):
214
+ if value is not None:
215
+ value = np.asanyarray(value, dtype=np.float32)
216
+ value = np.ascontiguousarray(value)
217
+ self._indices = value
218
+
219
+ @property
220
+ def material(self):
221
+ """:class:`Material` : The material for this primitive.
222
+ """
223
+ return self._material
224
+
225
+ @material.setter
226
+ def material(self, value):
227
+ # Create default material
228
+ if value is None:
229
+ value = MetallicRoughnessMaterial()
230
+ else:
231
+ if not isinstance(value, Material):
232
+ raise TypeError('Object material must be of type Material')
233
+ self._material = value
234
+
235
+ @property
236
+ def mode(self):
237
+ """int : The type of primitive to render.
238
+ """
239
+ return self._mode
240
+
241
+ @mode.setter
242
+ def mode(self, value):
243
+ value = int(value)
244
+ if value < GLTF.POINTS or value > GLTF.TRIANGLE_FAN:
245
+ raise ValueError('Invalid mode')
246
+ self._mode = value
247
+
248
+ @property
249
+ def targets(self):
250
+ """(k,) int : Morph target indices.
251
+ """
252
+ return self._targets
253
+
254
+ @targets.setter
255
+ def targets(self, value):
256
+ self._targets = value
257
+
258
+ @property
259
+ def poses(self):
260
+ """(x,4,4) float : Homogenous transforms for instancing this primitive.
261
+ """
262
+ return self._poses
263
+
264
+ @poses.setter
265
+ def poses(self, value):
266
+ if value is not None:
267
+ value = np.asanyarray(value, dtype=np.float32)
268
+ value = np.ascontiguousarray(value)
269
+ if value.ndim == 2:
270
+ value = value[np.newaxis,:,:]
271
+ if value.shape[1] != 4 or value.shape[2] != 4:
272
+ raise ValueError('Pose matrices must be of shape (n,4,4), '
273
+ 'got {}'.format(value.shape))
274
+ self._poses = value
275
+ self._bounds = None
276
+
277
+ @property
278
+ def bounds(self):
279
+ if self._bounds is None:
280
+ self._bounds = self._compute_bounds()
281
+ return self._bounds
282
+
283
+ @property
284
+ def centroid(self):
285
+ """(3,) float : The centroid of the primitive's AABB.
286
+ """
287
+ return np.mean(self.bounds, axis=0)
288
+
289
+ @property
290
+ def extents(self):
291
+ """(3,) float : The lengths of the axes of the primitive's AABB.
292
+ """
293
+ return np.diff(self.bounds, axis=0).reshape(-1)
294
+
295
+ @property
296
+ def scale(self):
297
+ """(3,) float : The length of the diagonal of the primitive's AABB.
298
+ """
299
+ return np.linalg.norm(self.extents)
300
+
301
+ @property
302
+ def buf_flags(self):
303
+ """int : The flags for the render buffer.
304
+ """
305
+ if self._buf_flags is None:
306
+ self._buf_flags = self._compute_buf_flags()
307
+ return self._buf_flags
308
+
309
+ def delete(self):
310
+ self._unbind()
311
+ self._remove_from_context()
312
+
313
+ @property
314
+ def is_transparent(self):
315
+ """bool : If True, the mesh is partially-transparent.
316
+ """
317
+ return self._compute_transparency()
318
+
319
+ def _add_to_context(self):
320
+ if self._vaid is not None:
321
+ raise ValueError('Mesh is already bound to a context')
322
+
323
+ # Generate and bind VAO
324
+ self._vaid = glGenVertexArrays(1)
325
+ glBindVertexArray(self._vaid)
326
+
327
+ #######################################################################
328
+ # Fill vertex buffer
329
+ #######################################################################
330
+
331
+ # Generate and bind vertex buffer
332
+ vertexbuffer = glGenBuffers(1)
333
+ self._buffers.append(vertexbuffer)
334
+ glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer)
335
+
336
+ # positions
337
+ vertex_data = self.positions
338
+ attr_sizes = [3]
339
+
340
+ # Normals
341
+ if self.normals is not None:
342
+ vertex_data = np.hstack((vertex_data, self.normals))
343
+ attr_sizes.append(3)
344
+
345
+ # Tangents
346
+ if self.tangents is not None:
347
+ vertex_data = np.hstack((vertex_data, self.tangents))
348
+ attr_sizes.append(4)
349
+
350
+ # Texture Coordinates
351
+ if self.texcoord_0 is not None:
352
+ vertex_data = np.hstack((vertex_data, self.texcoord_0))
353
+ attr_sizes.append(2)
354
+ if self.texcoord_1 is not None:
355
+ vertex_data = np.hstack((vertex_data, self.texcoord_1))
356
+ attr_sizes.append(2)
357
+
358
+ # Color
359
+ if self.color_0 is not None:
360
+ vertex_data = np.hstack((vertex_data, self.color_0))
361
+ attr_sizes.append(4)
362
+
363
+ # TODO JOINTS AND WEIGHTS
364
+ # PASS
365
+
366
+ # Copy data to buffer
367
+ vertex_data = np.ascontiguousarray(
368
+ vertex_data.flatten().astype(np.float32)
369
+ )
370
+ glBufferData(
371
+ GL_ARRAY_BUFFER, FLOAT_SZ * len(vertex_data),
372
+ vertex_data, GL_STATIC_DRAW
373
+ )
374
+ total_sz = sum(attr_sizes)
375
+ offset = 0
376
+ for i, sz in enumerate(attr_sizes):
377
+ glVertexAttribPointer(
378
+ i, sz, GL_FLOAT, GL_FALSE, FLOAT_SZ * total_sz,
379
+ ctypes.c_void_p(FLOAT_SZ * offset)
380
+ )
381
+ glEnableVertexAttribArray(i)
382
+ offset += sz
383
+
384
+ #######################################################################
385
+ # Fill model matrix buffer
386
+ #######################################################################
387
+
388
+ if self.poses is not None:
389
+ pose_data = np.ascontiguousarray(
390
+ np.transpose(self.poses, [0,2,1]).flatten().astype(np.float32)
391
+ )
392
+ else:
393
+ pose_data = np.ascontiguousarray(
394
+ np.eye(4).flatten().astype(np.float32)
395
+ )
396
+
397
+ modelbuffer = glGenBuffers(1)
398
+ self._buffers.append(modelbuffer)
399
+ glBindBuffer(GL_ARRAY_BUFFER, modelbuffer)
400
+ glBufferData(
401
+ GL_ARRAY_BUFFER, FLOAT_SZ * len(pose_data),
402
+ pose_data, GL_STATIC_DRAW
403
+ )
404
+
405
+ for i in range(0, 4):
406
+ idx = i + len(attr_sizes)
407
+ glEnableVertexAttribArray(idx)
408
+ glVertexAttribPointer(
409
+ idx, 4, GL_FLOAT, GL_FALSE, FLOAT_SZ * 4 * 4,
410
+ ctypes.c_void_p(4 * FLOAT_SZ * i)
411
+ )
412
+ glVertexAttribDivisor(idx, 1)
413
+
414
+ #######################################################################
415
+ # Fill element buffer
416
+ #######################################################################
417
+ if self.indices is not None:
418
+ elementbuffer = glGenBuffers(1)
419
+ self._buffers.append(elementbuffer)
420
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer)
421
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, UINT_SZ * self.indices.size,
422
+ self.indices.flatten().astype(np.uint32),
423
+ GL_STATIC_DRAW)
424
+
425
+ glBindVertexArray(0)
426
+
427
+ def _remove_from_context(self):
428
+ if self._vaid is not None:
429
+ glDeleteVertexArrays(1, [self._vaid])
430
+ glDeleteBuffers(len(self._buffers), self._buffers)
431
+ self._vaid = None
432
+ self._buffers = []
433
+
434
+ def _in_context(self):
435
+ return self._vaid is not None
436
+
437
+ def _bind(self):
438
+ if self._vaid is None:
439
+ raise ValueError('Cannot bind a Mesh that has not been added '
440
+ 'to a context')
441
+ glBindVertexArray(self._vaid)
442
+
443
+ def _unbind(self):
444
+ glBindVertexArray(0)
445
+
446
+ def _compute_bounds(self):
447
+ """Compute the bounds of this object.
448
+ """
449
+ # Compute bounds of this object
450
+ bounds = np.array([np.min(self.positions, axis=0),
451
+ np.max(self.positions, axis=0)])
452
+
453
+ # If instanced, compute translations for approximate bounds
454
+ if self.poses is not None:
455
+ bounds += np.array([np.min(self.poses[:,:3,3], axis=0),
456
+ np.max(self.poses[:,:3,3], axis=0)])
457
+ return bounds
458
+
459
+ def _compute_transparency(self):
460
+ """Compute whether or not this object is transparent.
461
+ """
462
+ if self.material.is_transparent:
463
+ return True
464
+ if self._is_transparent is None:
465
+ self._is_transparent = False
466
+ if self.color_0 is not None:
467
+ if np.any(self._color_0[:,3] != 1.0):
468
+ self._is_transparent = True
469
+ return self._is_transparent
470
+
471
+ def _compute_buf_flags(self):
472
+ buf_flags = BufFlags.POSITION
473
+
474
+ if self.normals is not None:
475
+ buf_flags |= BufFlags.NORMAL
476
+ if self.tangents is not None:
477
+ buf_flags |= BufFlags.TANGENT
478
+ if self.texcoord_0 is not None:
479
+ buf_flags |= BufFlags.TEXCOORD_0
480
+ if self.texcoord_1 is not None:
481
+ buf_flags |= BufFlags.TEXCOORD_1
482
+ if self.color_0 is not None:
483
+ buf_flags |= BufFlags.COLOR_0
484
+ if self.joints_0 is not None:
485
+ buf_flags |= BufFlags.JOINTS_0
486
+ if self.weights_0 is not None:
487
+ buf_flags |= BufFlags.WEIGHTS_0
488
+
489
+ return buf_flags