From c7eda7cb49f706040cb04048b24c6db57501889e Mon Sep 17 00:00:00 2001 From: Julien Duroure Date: Tue, 21 Jul 2020 21:18:35 +0200 Subject: glTF importer: add option to glue pieces of a mesh together Thanks scurest! --- io_scene_gltf2/__init__.py | 15 +++- io_scene_gltf2/blender/imp/gltf2_blender_mesh.py | 97 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 57bdf3d5..ba7b3848 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -15,7 +15,7 @@ bl_info = { 'name': 'glTF 2.0 format', 'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors', - "version": (1, 3, 39), + "version": (1, 3, 40), 'blender': (2, 90, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', @@ -862,6 +862,18 @@ class ImportGLTF2(Operator, ImportHelper): default=True ) + merge_vertices: BoolProperty( + name='Merge Vertices', + description=( + 'The glTF format requires discontinuous normals, UVs, and ' + 'other vertex attributes to be stored as separate vertices, ' + 'as required for rendering on typical graphics hardware.\n' + 'This option attempts to combine co-located vertices where possible.\n' + 'Currently cannot combine verts with different normals' + ), + default=False, + ) + import_shading: EnumProperty( name="Shading", items=(("NORMALS", "Use Normal Data", ""), @@ -906,6 +918,7 @@ class ImportGLTF2(Operator, ImportHelper): layout.use_property_decorate = False # No animation. layout.prop(self, 'import_pack_images') + layout.prop(self, 'merge_vertices') layout.prop(self, 'import_shading') layout.prop(self, 'guess_original_bind_pose') layout.prop(self, 'bone_heuristic') diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py index 33578de8..175bc920 100755 --- a/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_mesh.py @@ -217,6 +217,14 @@ def do_primitives(gltf, mesh_idx, skin_idx, mesh, ob): # the cache now that all prims are done. gltf.decode_accessor_cache = {} + if gltf.import_settings['merge_vertices']: + vert_locs, vert_normals, vert_joints, vert_weights, \ + sk_vert_locs, loop_vidxs, edge_vidxs = \ + merge_duplicate_verts( + vert_locs, vert_normals, vert_joints, vert_weights, \ + sk_vert_locs, loop_vidxs, edge_vidxs\ + ) + # --------------- # Convert all the arrays glTF -> Blender @@ -537,3 +545,92 @@ def skin_into_bind_pose(gltf, skin_idx, vert_joints, vert_weights, locs, vert_no def mul_mats_vecs(mats, vecs): """Given [m1,m2,...] and [v1,v2,...], returns [m1@v1,m2@v2,...]. 3D only.""" return np.matmul(mats, vecs.reshape(len(vecs), 3, 1)).reshape(len(vecs), 3) + + +def merge_duplicate_verts(vert_locs, vert_normals, vert_joints, vert_weights, sk_vert_locs, loop_vidxs, edge_vidxs): + # This function attempts to invert the splitting done when exporting to + # glTF. Welds together verts with the same per-vert data (but possibly + # different per-loop data). + # + # Ideally normals would be treated as per-loop data, but that has problems, + # so we currently treat the normal as per-vert. + # + # Strategy is simple: put all the per-vert data into an array of structs + # ("dots"), dedupe with np.unique, then take all the data back out. + + # Very often two verts that "morally" should be merged will have normals + # with very small differences. Round off the normals to smooth this over. + if len(vert_normals) != 0: + vert_normals *= 50000 + vert_normals[:] = np.trunc(vert_normals) + vert_normals *= (1/50000) + + dot_fields = [('x', np.float32), ('y', np.float32), ('z', np.float32)] + if len(vert_normals) != 0: + dot_fields += [('nx', np.float32), ('ny', np.float32), ('nz', np.float32)] + for i, _ in enumerate(vert_joints): + dot_fields += [ + ('joint%dx' % i, np.uint32), ('joint%dy' % i, np.uint32), + ('joint%dz' % i, np.uint32), ('joint%dw' % i, np.uint32), + ('weight%dx' % i, np.float32), ('weight%dy' % i, np.float32), + ('weight%dz' % i, np.float32), ('weight%dw' % i, np.float32), + ] + for i, _ in enumerate(sk_vert_locs): + dot_fields += [ + ('sk%dx' % i, np.float32), ('sk%dy' % i, np.float32), ('sk%dz' % i, np.float32), + ] + dots = np.empty(len(vert_locs), dtype=np.dtype(dot_fields)) + + dots['x'] = vert_locs[:, 0] + dots['y'] = vert_locs[:, 1] + dots['z'] = vert_locs[:, 2] + if len(vert_normals) != 0: + dots['nx'] = vert_normals[:, 0] + dots['ny'] = vert_normals[:, 1] + dots['nz'] = vert_normals[:, 2] + for i, (joints, weights) in enumerate(zip(vert_joints, vert_weights)): + dots['joint%dx' % i] = joints[:, 0] + dots['joint%dy' % i] = joints[:, 1] + dots['joint%dz' % i] = joints[:, 2] + dots['joint%dw' % i] = joints[:, 3] + dots['weight%dx' % i] = weights[:, 0] + dots['weight%dy' % i] = weights[:, 1] + dots['weight%dz' % i] = weights[:, 2] + dots['weight%dw' % i] = weights[:, 3] + for i, locs in enumerate(sk_vert_locs): + dots['sk%dx' % i] = locs[:, 0] + dots['sk%dy' % i] = locs[:, 1] + dots['sk%dz' % i] = locs[:, 2] + + unique_dots, inv_indices = np.unique(dots, return_inverse=True) + + loop_vidxs = inv_indices[loop_vidxs] + edge_vidxs = inv_indices[edge_vidxs] + + vert_locs = np.empty((len(unique_dots), 3), dtype=np.float32) + vert_locs[:, 0] = unique_dots['x'] + vert_locs[:, 1] = unique_dots['y'] + vert_locs[:, 2] = unique_dots['z'] + if len(vert_normals) != 0: + vert_normals = np.empty((len(unique_dots), 3), dtype=np.float32) + vert_normals[:, 0] = unique_dots['nx'] + vert_normals[:, 1] = unique_dots['ny'] + vert_normals[:, 2] = unique_dots['nz'] + for i in range(len(vert_joints)): + vert_joints[i] = np.empty((len(unique_dots), 4), dtype=np.uint32) + vert_joints[i][:, 0] = unique_dots['joint%dx' % i] + vert_joints[i][:, 1] = unique_dots['joint%dy' % i] + vert_joints[i][:, 2] = unique_dots['joint%dz' % i] + vert_joints[i][:, 3] = unique_dots['joint%dw' % i] + vert_weights[i] = np.empty((len(unique_dots), 4), dtype=np.float32) + vert_weights[i][:, 0] = unique_dots['weight%dx' % i] + vert_weights[i][:, 1] = unique_dots['weight%dy' % i] + vert_weights[i][:, 2] = unique_dots['weight%dz' % i] + vert_weights[i][:, 3] = unique_dots['weight%dw' % i] + for i in range(len(sk_vert_locs)): + sk_vert_locs[i] = np.empty((len(unique_dots), 3), dtype=np.float32) + sk_vert_locs[i][:, 0] = unique_dots['sk%dx' % i] + sk_vert_locs[i][:, 1] = unique_dots['sk%dy' % i] + sk_vert_locs[i][:, 2] = unique_dots['sk%dz' % i] + + return vert_locs, vert_normals, vert_joints, vert_weights, sk_vert_locs, loop_vidxs, edge_vidxs -- cgit v1.2.3