Spaces:
Runtime error
Runtime error
"""Primitives, conforming to the glTF 2.0 standards as specified in | |
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-primitive | |
Author: Matthew Matl | |
""" | |
import numpy as np | |
from OpenGL.GL import * | |
from .material import Material, MetallicRoughnessMaterial | |
from .constants import FLOAT_SZ, UINT_SZ, BufFlags, GLTF | |
from .utils import format_color_array | |
class Primitive(object): | |
"""A primitive object which can be rendered. | |
Parameters | |
---------- | |
positions : (n, 3) float | |
XYZ vertex positions. | |
normals : (n, 3) float | |
Normalized XYZ vertex normals. | |
tangents : (n, 4) float | |
XYZW vertex tangents where the w component is a sign value | |
(either +1 or -1) indicating the handedness of the tangent basis. | |
texcoord_0 : (n, 2) float | |
The first set of UV texture coordinates. | |
texcoord_1 : (n, 2) float | |
The second set of UV texture coordinates. | |
color_0 : (n, 4) float | |
RGBA vertex colors. | |
joints_0 : (n, 4) float | |
Joint information. | |
weights_0 : (n, 4) float | |
Weight information for morphing. | |
indices : (m, 3) int | |
Face indices for triangle meshes or fans. | |
material : :class:`Material` | |
The material to apply to this primitive when rendering. | |
mode : int | |
The type of primitives to render, one of the following: | |
- ``0``: POINTS | |
- ``1``: LINES | |
- ``2``: LINE_LOOP | |
- ``3``: LINE_STRIP | |
- ``4``: TRIANGLES | |
- ``5``: TRIANGLES_STRIP | |
- ``6``: TRIANGLES_FAN | |
targets : (k,) int | |
Morph target indices. | |
poses : (x,4,4), float | |
Array of 4x4 transformation matrices for instancing this object. | |
""" | |
def __init__(self, | |
positions, | |
normals=None, | |
tangents=None, | |
texcoord_0=None, | |
texcoord_1=None, | |
color_0=None, | |
joints_0=None, | |
weights_0=None, | |
indices=None, | |
material=None, | |
mode=None, | |
targets=None, | |
poses=None): | |
if mode is None: | |
mode = GLTF.TRIANGLES | |
self.positions = positions | |
self.normals = normals | |
self.tangents = tangents | |
self.texcoord_0 = texcoord_0 | |
self.texcoord_1 = texcoord_1 | |
self.color_0 = color_0 | |
self.joints_0 = joints_0 | |
self.weights_0 = weights_0 | |
self.indices = indices | |
self.material = material | |
self.mode = mode | |
self.targets = targets | |
self.poses = poses | |
self._bounds = None | |
self._vaid = None | |
self._buffers = [] | |
self._is_transparent = None | |
self._buf_flags = None | |
def positions(self): | |
"""(n,3) float : XYZ vertex positions. | |
""" | |
return self._positions | |
def positions(self, value): | |
value = np.asanyarray(value, dtype=np.float32) | |
self._positions = np.ascontiguousarray(value) | |
self._bounds = None | |
def normals(self): | |
"""(n,3) float : Normalized XYZ vertex normals. | |
""" | |
return self._normals | |
def normals(self, value): | |
if value is not None: | |
value = np.asanyarray(value, dtype=np.float32) | |
value = np.ascontiguousarray(value) | |
if value.shape != self.positions.shape: | |
raise ValueError('Incorrect normals shape') | |
self._normals = value | |
def tangents(self): | |
"""(n,4) float : XYZW vertex tangents. | |
""" | |
return self._tangents | |
def tangents(self, value): | |
if value is not None: | |
value = np.asanyarray(value, dtype=np.float32) | |
value = np.ascontiguousarray(value) | |
if value.shape != (self.positions.shape[0], 4): | |
raise ValueError('Incorrect tangent shape') | |
self._tangents = value | |
def texcoord_0(self): | |
"""(n,2) float : The first set of UV texture coordinates. | |
""" | |
return self._texcoord_0 | |
def texcoord_0(self, value): | |
if value is not None: | |
value = np.asanyarray(value, dtype=np.float32) | |
value = np.ascontiguousarray(value) | |
if (value.ndim != 2 or value.shape[0] != self.positions.shape[0] or | |
value.shape[1] < 2): | |
raise ValueError('Incorrect texture coordinate shape') | |
if value.shape[1] > 2: | |
value = value[:,:2] | |
self._texcoord_0 = value | |
def texcoord_1(self): | |
"""(n,2) float : The second set of UV texture coordinates. | |
""" | |
return self._texcoord_1 | |
def texcoord_1(self, value): | |
if value is not None: | |
value = np.asanyarray(value, dtype=np.float32) | |
value = np.ascontiguousarray(value) | |
if (value.ndim != 2 or value.shape[0] != self.positions.shape[0] or | |
value.shape[1] != 2): | |
raise ValueError('Incorrect texture coordinate shape') | |
self._texcoord_1 = value | |
def color_0(self): | |
"""(n,4) float : RGBA vertex colors. | |
""" | |
return self._color_0 | |
def color_0(self, value): | |
if value is not None: | |
value = np.ascontiguousarray( | |
format_color_array(value, shape=(len(self.positions), 4)) | |
) | |
self._is_transparent = None | |
self._color_0 = value | |
def joints_0(self): | |
"""(n,4) float : Joint information. | |
""" | |
return self._joints_0 | |
def joints_0(self, value): | |
self._joints_0 = value | |
def weights_0(self): | |
"""(n,4) float : Weight information for morphing. | |
""" | |
return self._weights_0 | |
def weights_0(self, value): | |
self._weights_0 = value | |
def indices(self): | |
"""(m,3) int : Face indices for triangle meshes or fans. | |
""" | |
return self._indices | |
def indices(self, value): | |
if value is not None: | |
value = np.asanyarray(value, dtype=np.float32) | |
value = np.ascontiguousarray(value) | |
self._indices = value | |
def material(self): | |
""":class:`Material` : The material for this primitive. | |
""" | |
return self._material | |
def material(self, value): | |
# Create default material | |
if value is None: | |
value = MetallicRoughnessMaterial() | |
else: | |
if not isinstance(value, Material): | |
raise TypeError('Object material must be of type Material') | |
self._material = value | |
def mode(self): | |
"""int : The type of primitive to render. | |
""" | |
return self._mode | |
def mode(self, value): | |
value = int(value) | |
if value < GLTF.POINTS or value > GLTF.TRIANGLE_FAN: | |
raise ValueError('Invalid mode') | |
self._mode = value | |
def targets(self): | |
"""(k,) int : Morph target indices. | |
""" | |
return self._targets | |
def targets(self, value): | |
self._targets = value | |
def poses(self): | |
"""(x,4,4) float : Homogenous transforms for instancing this primitive. | |
""" | |
return self._poses | |
def poses(self, value): | |
if value is not None: | |
value = np.asanyarray(value, dtype=np.float32) | |
value = np.ascontiguousarray(value) | |
if value.ndim == 2: | |
value = value[np.newaxis,:,:] | |
if value.shape[1] != 4 or value.shape[2] != 4: | |
raise ValueError('Pose matrices must be of shape (n,4,4), ' | |
'got {}'.format(value.shape)) | |
self._poses = value | |
self._bounds = None | |
def bounds(self): | |
if self._bounds is None: | |
self._bounds = self._compute_bounds() | |
return self._bounds | |
def centroid(self): | |
"""(3,) float : The centroid of the primitive's AABB. | |
""" | |
return np.mean(self.bounds, axis=0) | |
def extents(self): | |
"""(3,) float : The lengths of the axes of the primitive's AABB. | |
""" | |
return np.diff(self.bounds, axis=0).reshape(-1) | |
def scale(self): | |
"""(3,) float : The length of the diagonal of the primitive's AABB. | |
""" | |
return np.linalg.norm(self.extents) | |
def buf_flags(self): | |
"""int : The flags for the render buffer. | |
""" | |
if self._buf_flags is None: | |
self._buf_flags = self._compute_buf_flags() | |
return self._buf_flags | |
def delete(self): | |
self._unbind() | |
self._remove_from_context() | |
def is_transparent(self): | |
"""bool : If True, the mesh is partially-transparent. | |
""" | |
return self._compute_transparency() | |
def _add_to_context(self): | |
if self._vaid is not None: | |
raise ValueError('Mesh is already bound to a context') | |
# Generate and bind VAO | |
self._vaid = glGenVertexArrays(1) | |
glBindVertexArray(self._vaid) | |
####################################################################### | |
# Fill vertex buffer | |
####################################################################### | |
# Generate and bind vertex buffer | |
vertexbuffer = glGenBuffers(1) | |
self._buffers.append(vertexbuffer) | |
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer) | |
# positions | |
vertex_data = self.positions | |
attr_sizes = [3] | |
# Normals | |
if self.normals is not None: | |
vertex_data = np.hstack((vertex_data, self.normals)) | |
attr_sizes.append(3) | |
# Tangents | |
if self.tangents is not None: | |
vertex_data = np.hstack((vertex_data, self.tangents)) | |
attr_sizes.append(4) | |
# Texture Coordinates | |
if self.texcoord_0 is not None: | |
vertex_data = np.hstack((vertex_data, self.texcoord_0)) | |
attr_sizes.append(2) | |
if self.texcoord_1 is not None: | |
vertex_data = np.hstack((vertex_data, self.texcoord_1)) | |
attr_sizes.append(2) | |
# Color | |
if self.color_0 is not None: | |
vertex_data = np.hstack((vertex_data, self.color_0)) | |
attr_sizes.append(4) | |
# TODO JOINTS AND WEIGHTS | |
# PASS | |
# Copy data to buffer | |
vertex_data = np.ascontiguousarray( | |
vertex_data.flatten().astype(np.float32) | |
) | |
glBufferData( | |
GL_ARRAY_BUFFER, FLOAT_SZ * len(vertex_data), | |
vertex_data, GL_STATIC_DRAW | |
) | |
total_sz = sum(attr_sizes) | |
offset = 0 | |
for i, sz in enumerate(attr_sizes): | |
glVertexAttribPointer( | |
i, sz, GL_FLOAT, GL_FALSE, FLOAT_SZ * total_sz, | |
ctypes.c_void_p(FLOAT_SZ * offset) | |
) | |
glEnableVertexAttribArray(i) | |
offset += sz | |
####################################################################### | |
# Fill model matrix buffer | |
####################################################################### | |
if self.poses is not None: | |
pose_data = np.ascontiguousarray( | |
np.transpose(self.poses, [0,2,1]).flatten().astype(np.float32) | |
) | |
else: | |
pose_data = np.ascontiguousarray( | |
np.eye(4).flatten().astype(np.float32) | |
) | |
modelbuffer = glGenBuffers(1) | |
self._buffers.append(modelbuffer) | |
glBindBuffer(GL_ARRAY_BUFFER, modelbuffer) | |
glBufferData( | |
GL_ARRAY_BUFFER, FLOAT_SZ * len(pose_data), | |
pose_data, GL_STATIC_DRAW | |
) | |
for i in range(0, 4): | |
idx = i + len(attr_sizes) | |
glEnableVertexAttribArray(idx) | |
glVertexAttribPointer( | |
idx, 4, GL_FLOAT, GL_FALSE, FLOAT_SZ * 4 * 4, | |
ctypes.c_void_p(4 * FLOAT_SZ * i) | |
) | |
glVertexAttribDivisor(idx, 1) | |
####################################################################### | |
# Fill element buffer | |
####################################################################### | |
if self.indices is not None: | |
elementbuffer = glGenBuffers(1) | |
self._buffers.append(elementbuffer) | |
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer) | |
glBufferData(GL_ELEMENT_ARRAY_BUFFER, UINT_SZ * self.indices.size, | |
self.indices.flatten().astype(np.uint32), | |
GL_STATIC_DRAW) | |
glBindVertexArray(0) | |
def _remove_from_context(self): | |
if self._vaid is not None: | |
glDeleteVertexArrays(1, [self._vaid]) | |
glDeleteBuffers(len(self._buffers), self._buffers) | |
self._vaid = None | |
self._buffers = [] | |
def _in_context(self): | |
return self._vaid is not None | |
def _bind(self): | |
if self._vaid is None: | |
raise ValueError('Cannot bind a Mesh that has not been added ' | |
'to a context') | |
glBindVertexArray(self._vaid) | |
def _unbind(self): | |
glBindVertexArray(0) | |
def _compute_bounds(self): | |
"""Compute the bounds of this object. | |
""" | |
# Compute bounds of this object | |
bounds = np.array([np.min(self.positions, axis=0), | |
np.max(self.positions, axis=0)]) | |
# If instanced, compute translations for approximate bounds | |
if self.poses is not None: | |
bounds += np.array([np.min(self.poses[:,:3,3], axis=0), | |
np.max(self.poses[:,:3,3], axis=0)]) | |
return bounds | |
def _compute_transparency(self): | |
"""Compute whether or not this object is transparent. | |
""" | |
if self.material.is_transparent: | |
return True | |
if self._is_transparent is None: | |
self._is_transparent = False | |
if self.color_0 is not None: | |
if np.any(self._color_0[:,3] != 1.0): | |
self._is_transparent = True | |
return self._is_transparent | |
def _compute_buf_flags(self): | |
buf_flags = BufFlags.POSITION | |
if self.normals is not None: | |
buf_flags |= BufFlags.NORMAL | |
if self.tangents is not None: | |
buf_flags |= BufFlags.TANGENT | |
if self.texcoord_0 is not None: | |
buf_flags |= BufFlags.TEXCOORD_0 | |
if self.texcoord_1 is not None: | |
buf_flags |= BufFlags.TEXCOORD_1 | |
if self.color_0 is not None: | |
buf_flags |= BufFlags.COLOR_0 | |
if self.joints_0 is not None: | |
buf_flags |= BufFlags.JOINTS_0 | |
if self.weights_0 is not None: | |
buf_flags |= BufFlags.WEIGHTS_0 | |
return buf_flags | |