Ejemplo n.º 1
0
    def ensure_uniform_scale(self):
        if self.is_bounding_box:
            return

        l, r, s = decompose(self.matrix_local)

        if not np.allclose(s[:1], s[1:], rtol=0, atol=0.001):
            print(f"[INFO] {self.name} has non-uniform scale")

            if self.source.type == "MESH":
                self.vertex_transform = s

            self.matrix_local = compose(l, r, 1)
            for child in self.children:
                child.matrix_local[:3, :3] *= s

        for child in self.children:
            child.ensure_uniform_scale()
Ejemplo n.º 2
0
    def correct_rest_positions(self):
        """Correct the rest pose of the root bone.

        The rest pose of vanilla assets often feature inconvenient transforms.
        This is not an issue in-game since you would only ever see actors with
        their animations applied. When working in Blender however, a sane rest
        pose will make the lives of artists much easier.

        This function replaces the root bone's rest matrix with an edited copy
        of its posed matrix. This edited copy is identical to pose matrix with
        regards to location and scale, but has had its rotation about all axes
        aligned to the nearest 90 degree angle.
        """
        if not self.armatures:
            return

        root = self.get_armature_node()
        root_bone = next(self.iter_bones(root))

        # calculate corrected transformation matrix
        l, r, s = decompose(root_bone.matrix_posed)
        r = nif_utils.snap_rotation(r)
        corrected_matrix = compose(l, r, s)

        # only do corrections if they are necessary
        if np.allclose(root_bone.matrix_world,
                       corrected_matrix,
                       rtol=0,
                       atol=1e-6):
            return

        # correct the rest matrix of skinned meshes
        inverse = la.inv(root_bone.matrix_world)
        for node in self.get_skinned_meshes():
            if root_bone not in node.parents:
                node.matrix_world = corrected_matrix @ (
                    inverse @ node.matrix_world)

        # correct the rest matrix of the root bone
        root_bone.matrix_world = corrected_matrix
Ejemplo n.º 3
0
    def correct_rest_positions(self):
        if not self.armatures:
            return

        root = self.get(*self.armatures)  # __history__
        root_bone = next(self.iter_bones(root))  # __history__

        # calculate corrected transformation matrix
        l, r, s = decompose(root_bone.matrix_posed)
        r = nif_utils.snap_rotation(r)
        corrected_matrix = compose(l, r, s)

        # correct the rest matrix of skinned meshes
        bone_inverse = la.inv(root_bone.matrix_world)
        for node in self.nodes:
            skin = getattr(node.source, "skin", None)
            if skin and (skin.root is root.source) and (root_bone
                                                        not in node.parents):
                node.matrix_world = corrected_matrix @ bone_inverse @ node.matrix_world

        # correct the rest matrix of the root bone
        root_bone.matrix_world = corrected_matrix
Ejemplo n.º 4
0
 def matrix(self, value):
     self.center, self.rotation, self.extents = decompose(value)
Ejemplo n.º 5
0
 def create_bounding_volume(self):
     c, r, e = decompose(self.matrix_world)
     e *= self.source.empty_display_size
     self.output.bounding_volume = nif.NiBoxBV(center=c, rotation=r, extents=e)
     self.output.matrix = ID44
Ejemplo n.º 6
0
 def matrix(self, value: ndarray):
     self.center, self.axes, self.extents = decompose(value)
Ejemplo n.º 7
0
    def get_mesh_data(self):
        vertices = self.source.data.vertices
        normals = self.source.data.normals
        uv_sets = self.source.data.uv_sets.copy()
        vertex_colors = self.source.data.vertex_colors
        vertex_weights = self.source.vertex_weights()
        vertex_morphs = self.source.vertex_morphs()
        triangles = self.source.data.triangles

        if len(normals):
            # re-unitize, fixes landscape meshes
            normals /= la.norm(normals, axis=1, keepdims=True)
            # reconstruct as per-triangle layout
            normals = normals[triangles].reshape(-1, 3)

        if len(uv_sets):
            # convert OpenGL into Blender format
            uv_sets[..., 1] = 1 - uv_sets[..., 1]
            # reconstruct as per-triangle layout
            uv_sets = uv_sets[:, triangles].reshape(-1, triangles.size, 2)

        if len(vertex_colors):
            # reconstruct as per-triangle layout
            vertex_colors = vertex_colors[triangles].reshape(-1, 3)

        # remove doubles
        scale = decompose(self.matrix_world)[-1]
        indices, inverse = nif_utils.unique_rows(
            vertices * scale,
            *vertex_weights,
            *vertex_morphs,
            precision=self.importer.vertex_precision,
        )
        if len(vertices) > len(indices) > 3:
            vertices = vertices[indices]
            vertex_weights = vertex_weights[:, indices]
            vertex_morphs = vertex_morphs[:, indices]
            triangles = inverse[triangles]

        # '''
        # Blender does not allow two faces to use identical vertex indices, regardless of order.
        # This is problematic as such occurances are commonly found throughout most nif data sets.
        # The usual case is "double-sided" faces, which share vertex indices but differ in winding.
        # Identify the problematic faces and duplicate their vertices to ensure the indices are unique.
        uniques, indices = np.unique(np.sort(triangles, axis=1),
                                     axis=0,
                                     return_index=True)
        if len(triangles) > len(uniques):
            # boolean mask of the triangles to be updated
            target_faces = np.full(len(triangles), True)
            target_faces[indices] = False

            # indices of the vertices that must be copied
            target_verts = triangles[target_faces].ravel()

            # find the vertices used in problematic faces
            new_vertices = vertices[target_verts]
            new_vertex_weights = vertex_weights[:, target_verts]
            new_vertex_morphs = vertex_morphs[:, target_verts]
            new_vertex_indices = np.arange(len(new_vertices)) + len(vertices)

            # update our final mesh data with new geometry
            vertices = np.vstack((vertices, new_vertices))
            vertex_weights = np.hstack((vertex_weights, new_vertex_weights))
            vertex_morphs = np.hstack((vertex_morphs, new_vertex_morphs))

            # copy is needed since shapes could share data
            triangles = triangles.copy()
            triangles[target_faces] = new_vertex_indices.reshape(-1, 3)
        # '''

        return nif_utils.Namespace(
            triangles=triangles,
            vertices=vertices,
            normals=normals,
            uv_sets=uv_sets,
            vertex_colors=vertex_colors,
            vertex_weights=vertex_weights,
            vertex_morphs=vertex_morphs,
        )