OpenMotionLab/MotionGPT
118
1"""Nodes, conforming to the glTF 2.0 standards as specified in2https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-node3 4Author: Matthew Matl5"""6import numpy as np7 8import trimesh.transformations as transformations9 10from .camera import Camera11from .mesh import Mesh12from .light import Light13 14 15class Node(object):16 """A node in the node hierarchy.17 18 Parameters19 ----------20 name : str, optional21 The user-defined name of this object.22 camera : :class:`Camera`, optional23 The camera in this node.24 children : list of :class:`Node`25 The children of this node.26 skin : int, optional27 The index of the skin referenced by this node.28 matrix : (4,4) float, optional29 A floating-point 4x4 transformation matrix.30 mesh : :class:`Mesh`, optional31 The mesh in this node.32 rotation : (4,) float, optional33 The node's unit quaternion in the order (x, y, z, w), where34 w is the scalar.35 scale : (3,) float, optional36 The node's non-uniform scale, given as the scaling factors along the x,37 y, and z axes.38 translation : (3,) float, optional39 The node's translation along the x, y, and z axes.40 weights : (n,) float41 The weights of the instantiated Morph Target. Number of elements must42 match number of Morph Targets of used mesh.43 light : :class:`Light`, optional44 The light in this node.45 """46 47 def __init__(self,48 name=None,49 camera=None,50 children=None,51 skin=None,52 matrix=None,53 mesh=None,54 rotation=None,55 scale=None,56 translation=None,57 weights=None,58 light=None):59 # Set defaults60 if children is None:61 children = []62 63 self._matrix = None64 self._scale = None65 self._rotation = None66 self._translation = None67 if matrix is None:68 if rotation is None:69 rotation = np.array([0.0, 0.0, 0.0, 1.0])70 if translation is None:71 translation = np.zeros(3)72 if scale is None:73 scale = np.ones(3)74 self.rotation = rotation75 self.translation = translation76 self.scale = scale77 else:78 self.matrix = matrix79 80 self.name = name81 self.camera = camera82 self.children = children83 self.skin = skin84 self.mesh = mesh85 self.weights = weights86 self.light = light87 88 @property89 def name(self):90 """str : The user-defined name of this object.91 """92 return self._name93 94 @name.setter95 def name(self, value):96 if value is not None:97 value = str(value)98 self._name = value99 100 @property101 def camera(self):102 """:class:`Camera` : The camera in this node.103 """104 return self._camera105 106 @camera.setter107 def camera(self, value):108 if value is not None and not isinstance(value, Camera):109 raise TypeError('Value must be a camera')110 self._camera = value111 112 @property113 def children(self):114 """list of :class:`Node` : The children of this node.115 """116 return self._children117 118 @children.setter119 def children(self, value):120 self._children = value121 122 @property123 def skin(self):124 """int : The skin index for this node.125 """126 return self._skin127 128 @skin.setter129 def skin(self, value):130 self._skin = value131 132 @property133 def mesh(self):134 """:class:`Mesh` : The mesh in this node.135 """136 return self._mesh137 138 @mesh.setter139 def mesh(self, value):140 if value is not None and not isinstance(value, Mesh):141 raise TypeError('Value must be a mesh')142 self._mesh = value143 144 @property145 def light(self):146 """:class:`Light` : The light in this node.147 """148 return self._light149 150 @light.setter151 def light(self, value):152 if value is not None and not isinstance(value, Light):153 raise TypeError('Value must be a light')154 self._light = value155 156 @property157 def rotation(self):158 """(4,) float : The xyzw quaternion for this node.159 """160 return self._rotation161 162 @rotation.setter163 def rotation(self, value):164 value = np.asanyarray(value)165 if value.shape != (4,):166 raise ValueError('Quaternion must be a (4,) vector')167 if np.abs(np.linalg.norm(value) - 1.0) > 1e-3:168 raise ValueError('Quaternion must have norm == 1.0')169 self._rotation = value170 self._matrix = None171 172 @property173 def translation(self):174 """(3,) float : The translation for this node.175 """176 return self._translation177 178 @translation.setter179 def translation(self, value):180 value = np.asanyarray(value)181 if value.shape != (3,):182 raise ValueError('Translation must be a (3,) vector')183 self._translation = value184 self._matrix = None185 186 @property187 def scale(self):188 """(3,) float : The scale for this node.189 """190 return self._scale191 192 @scale.setter193 def scale(self, value):194 value = np.asanyarray(value)195 if value.shape != (3,):196 raise ValueError('Scale must be a (3,) vector')197 self._scale = value198 self._matrix = None199 200 @property201 def matrix(self):202 """(4,4) float : The homogenous transform matrix for this node.203 204 Note that this matrix's elements are not settable,205 it's just a copy of the internal matrix. You can set the whole206 matrix, but not an individual element.207 """208 if self._matrix is None:209 self._matrix = self._m_from_tqs(210 self.translation, self.rotation, self.scale211 )212 return self._matrix.copy()213 214 @matrix.setter215 def matrix(self, value):216 value = np.asanyarray(value)217 if value.shape != (4,4):218 raise ValueError('Matrix must be a 4x4 numpy ndarray')219 if not np.allclose(value[3,:], np.array([0.0, 0.0, 0.0, 1.0])):220 raise ValueError('Bottom row of matrix must be [0,0,0,1]')221 self.rotation = Node._q_from_m(value)222 self.scale = Node._s_from_m(value)223 self.translation = Node._t_from_m(value)224 self._matrix = value225 226 @staticmethod227 def _t_from_m(m):228 return m[:3,3]229 230 @staticmethod231 def _r_from_m(m):232 U = m[:3,:3]233 norms = np.linalg.norm(U.T, axis=1)234 return U / norms235 236 @staticmethod237 def _q_from_m(m):238 M = np.eye(4)239 M[:3,:3] = Node._r_from_m(m)240 q_wxyz = transformations.quaternion_from_matrix(M)241 return np.roll(q_wxyz, -1)242 243 @staticmethod244 def _s_from_m(m):245 return np.linalg.norm(m[:3,:3].T, axis=1)246 247 @staticmethod248 def _r_from_q(q):249 q_wxyz = np.roll(q, 1)250 return transformations.quaternion_matrix(q_wxyz)[:3,:3]251 252 @staticmethod253 def _m_from_tqs(t, q, s):254 S = np.eye(4)255 S[:3,:3] = np.diag(s)256 257 R = np.eye(4)258 R[:3,:3] = Node._r_from_q(q)259 260 T = np.eye(4)261 T[:3,3] = t262 263 return T.dot(R.dot(S))264 