forked from LeenkxTeam/LNXSDK
		
	Update Files
This commit is contained in:
		
							
								
								
									
										445
									
								
								leenkx/blender/lnx/exporter_opt.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										445
									
								
								leenkx/blender/lnx/exporter_opt.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,445 @@ | ||||
| """ | ||||
| Exports smaller geometry but is slower. | ||||
| To be replaced with https://github.com/zeux/meshoptimizer | ||||
| """ | ||||
| from typing import Optional | ||||
|  | ||||
| import bpy | ||||
| from mathutils import Vector | ||||
| import numpy as np | ||||
|  | ||||
| import lnx.utils | ||||
| from lnx import log | ||||
|  | ||||
| if lnx.is_reload(__name__): | ||||
|     log = lnx.reload_module(log) | ||||
|     lnx.utils = lnx.reload_module(lnx.utils) | ||||
| else: | ||||
|     lnx.enable_reload(__name__) | ||||
|  | ||||
|  | ||||
| class Vertex: | ||||
|     __slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index") | ||||
|  | ||||
|     def __init__(self, mesh: bpy.types.Mesh, loop: bpy.types.MeshLoop, vcol0: Optional[bpy.types.Attribute]): | ||||
|         self.vertex_index = loop.vertex_index | ||||
|         loop_idx = loop.index | ||||
|         self.co = mesh.vertices[self.vertex_index].co[:] | ||||
|         self.normal = loop.normal[:] | ||||
|         self.uvs = tuple(layer.data[loop_idx].uv[:] for layer in mesh.uv_layers) | ||||
|         self.col = [0.0, 0.0, 0.0] if vcol0 is None else vcol0.data[loop_idx].color[:] | ||||
|         self.loop_indices = [loop_idx] | ||||
|         self.index = 0 | ||||
|  | ||||
|     def __hash__(self): | ||||
|         return hash((self.co, self.normal, self.uvs)) | ||||
|  | ||||
|     def __eq__(self, other): | ||||
|         eq = ( | ||||
|             (self.co == other.co) and | ||||
|             (self.normal == other.normal) and | ||||
|             (self.uvs == other.uvs) and | ||||
|             (self.col == other.col) | ||||
|             ) | ||||
|         if eq: | ||||
|             indices = self.loop_indices + other.loop_indices | ||||
|             self.loop_indices = indices | ||||
|             other.loop_indices = indices | ||||
|         return eq | ||||
|  | ||||
|  | ||||
| def calc_tangents(posa, nora, uva, ias, scale_pos): | ||||
|     num_verts = int(len(posa) / 4) | ||||
|     tangents = np.empty(num_verts * 3, dtype='<f4') | ||||
|     # bitangents = np.empty(num_verts * 3, dtype='<f4') | ||||
|     for ar in ias: | ||||
|         ia = ar['values'] | ||||
|         num_tris = int(len(ia) / 3) | ||||
|         for i in range(0, num_tris): | ||||
|             i0 = ia[i * 3    ] | ||||
|             i1 = ia[i * 3 + 1] | ||||
|             i2 = ia[i * 3 + 2] | ||||
|             v0 = Vector((posa[i0 * 4], posa[i0 * 4 + 1], posa[i0 * 4 + 2])) | ||||
|             v1 = Vector((posa[i1 * 4], posa[i1 * 4 + 1], posa[i1 * 4 + 2])) | ||||
|             v2 = Vector((posa[i2 * 4], posa[i2 * 4 + 1], posa[i2 * 4 + 2])) | ||||
|             uv0 = Vector((uva[i0 * 2], uva[i0 * 2 + 1])) | ||||
|             uv1 = Vector((uva[i1 * 2], uva[i1 * 2 + 1])) | ||||
|             uv2 = Vector((uva[i2 * 2], uva[i2 * 2 + 1])) | ||||
|  | ||||
|             deltaPos1 = v1 - v0 | ||||
|             deltaPos2 = v2 - v0 | ||||
|             deltaUV1 = uv1 - uv0 | ||||
|             deltaUV2 = uv2 - uv0 | ||||
|             d = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x) | ||||
|             if d != 0: | ||||
|                 r = 1.0 / d | ||||
|             else: | ||||
|                 r = 1.0 | ||||
|             tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r | ||||
|             # bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r | ||||
|  | ||||
|             tangents[i0 * 3    ] += tangent.x | ||||
|             tangents[i0 * 3 + 1] += tangent.y | ||||
|             tangents[i0 * 3 + 2] += tangent.z | ||||
|             tangents[i1 * 3    ] += tangent.x | ||||
|             tangents[i1 * 3 + 1] += tangent.y | ||||
|             tangents[i1 * 3 + 2] += tangent.z | ||||
|             tangents[i2 * 3    ] += tangent.x | ||||
|             tangents[i2 * 3 + 1] += tangent.y | ||||
|             tangents[i2 * 3 + 2] += tangent.z | ||||
|             # bitangents[i0 * 3    ] += bitangent.x | ||||
|             # bitangents[i0 * 3 + 1] += bitangent.y | ||||
|             # bitangents[i0 * 3 + 2] += bitangent.z | ||||
|             # bitangents[i1 * 3    ] += bitangent.x | ||||
|             # bitangents[i1 * 3 + 1] += bitangent.y | ||||
|             # bitangents[i1 * 3 + 2] += bitangent.z | ||||
|             # bitangents[i2 * 3    ] += bitangent.x | ||||
|             # bitangents[i2 * 3 + 1] += bitangent.y | ||||
|             # bitangents[i2 * 3 + 2] += bitangent.z | ||||
|     # Orthogonalize | ||||
|     for i in range(0, num_verts): | ||||
|         t = Vector((tangents[i * 3], tangents[i * 3 + 1], tangents[i * 3 + 2])) | ||||
|         # b = Vector((bitangents[i * 3], bitangents[i * 3 + 1], bitangents[i * 3 + 2])) | ||||
|         n = Vector((nora[i * 2], nora[i * 2 + 1], posa[i * 4 + 3] / scale_pos)) | ||||
|         v = t - n * n.dot(t) | ||||
|         v.normalize() | ||||
|         # Calculate handedness | ||||
|         # cnv = n.cross(v) | ||||
|         # if cnv.dot(b) < 0.0: | ||||
|             # v = v * -1.0 | ||||
|         tangents[i * 3    ] = v.x | ||||
|         tangents[i * 3 + 1] = v.y | ||||
|         tangents[i * 3 + 2] = v.z | ||||
|     return tangents | ||||
|  | ||||
|  | ||||
| def export_mesh_data(self, export_mesh: bpy.types.Mesh, bobject: bpy.types.Object, o, has_armature=False): | ||||
|     if bpy.app.version < (4, 1, 0): | ||||
|         export_mesh.calc_normals_split() | ||||
|     else: | ||||
|         updated_normals = export_mesh.corner_normals | ||||
|     # exportMesh.calc_loop_triangles() | ||||
|     vcol0 = self.get_nth_vertex_colors(export_mesh, 0) | ||||
|     vert_list = {Vertex(export_mesh, loop, vcol0): 0 for loop in export_mesh.loops}.keys() | ||||
|     num_verts = len(vert_list) | ||||
|     num_uv_layers = len(export_mesh.uv_layers) | ||||
|     # Check if shape keys were exported | ||||
|     has_morph_target = self.get_shape_keys(bobject.data) | ||||
|     if has_morph_target: | ||||
|         # Shape keys UV are exported separately, so reduce UV count by 1 | ||||
|         num_uv_layers -= 1 | ||||
|         morph_uv_index = self.get_morph_uv_index(bobject.data) | ||||
|     has_tex = self.get_export_uvs(export_mesh) and num_uv_layers > 0 | ||||
|     if self.has_baked_material(bobject, export_mesh.materials): | ||||
|         has_tex = True | ||||
|     has_tex1 = has_tex and num_uv_layers > 1 | ||||
|     num_colors = self.get_num_vertex_colors(export_mesh) | ||||
|     has_col = self.get_export_vcols(export_mesh) and num_colors > 0 | ||||
|     has_tang = self.has_tangents(export_mesh) | ||||
|  | ||||
|     pdata = np.empty(num_verts * 4, dtype='<f4') # p.xyz, n.z | ||||
|     ndata = np.empty(num_verts * 2, dtype='<f4') # n.xy | ||||
|     if has_tex or has_morph_target: | ||||
|         uv_layers = export_mesh.uv_layers | ||||
|         maxdim = 1.0 | ||||
|         maxdim_uvlayer = None | ||||
|         if has_tex: | ||||
|             t0map = 0 # Get active uvmap | ||||
|             t0data = np.empty(num_verts * 2, dtype='<f4') | ||||
|             if uv_layers is not None: | ||||
|                 if 'UVMap_baked' in uv_layers: | ||||
|                     for i in range(0, len(uv_layers)): | ||||
|                         if uv_layers[i].name == 'UVMap_baked': | ||||
|                             t0map = i | ||||
|                             break | ||||
|                 else: | ||||
|                     for i in range(0, len(uv_layers)): | ||||
|                         if uv_layers[i].active_render and uv_layers[i].name != 'UVMap_shape_key': | ||||
|                             t0map = i | ||||
|                             break | ||||
|             if has_tex1: | ||||
|                 for i in range(0, len(uv_layers)): | ||||
|                     # Not UVMap 0 | ||||
|                     if i != t0map: | ||||
|                         # Not Shape Key UVMap | ||||
|                         if has_morph_target and uv_layers[i].name == 'UVMap_shape_key': | ||||
|                             continue | ||||
|                         # Neither UVMap 0 Nor Shape Key Map | ||||
|                         t1map = i | ||||
|                 t1data = np.empty(num_verts * 2, dtype='<f4') | ||||
|             # Scale for packed coords | ||||
|             lay0 = uv_layers[t0map] | ||||
|             maxdim_uvlayer = lay0 | ||||
|             for v in lay0.data: | ||||
|                 if abs(v.uv[0]) > maxdim: | ||||
|                     maxdim = abs(v.uv[0]) | ||||
|                 if abs(v.uv[1]) > maxdim: | ||||
|                     maxdim = abs(v.uv[1]) | ||||
|             if has_tex1: | ||||
|                 lay1 = uv_layers[t1map] | ||||
|                 for v in lay1.data: | ||||
|                     if abs(v.uv[0]) > maxdim: | ||||
|                         maxdim = abs(v.uv[0]) | ||||
|                         maxdim_uvlayer = lay1 | ||||
|                     if abs(v.uv[1]) > maxdim: | ||||
|                         maxdim = abs(v.uv[1]) | ||||
|                         maxdim_uvlayer = lay1 | ||||
|         if has_morph_target: | ||||
|             morph_data = np.empty(num_verts * 2, dtype='<f4') | ||||
|             lay2 = uv_layers[morph_uv_index] | ||||
|             for v in lay2.data: | ||||
|                 if abs(v.uv[0]) > maxdim: | ||||
|                     maxdim = abs(v.uv[0]) | ||||
|                     maxdim_uvlayer = lay2 | ||||
|                 if abs(v.uv[1]) > maxdim: | ||||
|                     maxdim = abs(v.uv[1]) | ||||
|                     maxdim_uvlayer = lay2 | ||||
|         if maxdim > 1: | ||||
|             o['scale_tex'] = maxdim | ||||
|             invscale_tex = (1 / o['scale_tex']) * 32767 | ||||
|         else: | ||||
|             invscale_tex = 1 * 32767 | ||||
|         self.check_uv_precision(export_mesh, maxdim, maxdim_uvlayer, invscale_tex) | ||||
|  | ||||
|     if has_col: | ||||
|         cdata = np.empty(num_verts * 3, dtype='<f4') | ||||
|  | ||||
|     # Save aabb | ||||
|     self.calc_aabb(bobject) | ||||
|  | ||||
|     # Scale for packed coords | ||||
|     maxdim = max(bobject.data.lnx_aabb[0], max(bobject.data.lnx_aabb[1], bobject.data.lnx_aabb[2])) | ||||
|     if maxdim > 2: | ||||
|         o['scale_pos'] = maxdim / 2 | ||||
|     else: | ||||
|         o['scale_pos'] = 1.0 | ||||
|     if has_armature: # Allow up to 2x bigger bounds for skinned mesh | ||||
|         o['scale_pos'] *= 2.0 | ||||
|  | ||||
|     scale_pos = o['scale_pos'] | ||||
|     invscale_pos = (1 / scale_pos) * 32767 | ||||
|  | ||||
|     # Make arrays | ||||
|     for i, v in enumerate(vert_list): | ||||
|         v.index = i | ||||
|         co = v.co | ||||
|         normal = v.normal | ||||
|         i4 = i * 4 | ||||
|         i2 = i * 2 | ||||
|         pdata[i4    ] = co[0] | ||||
|         pdata[i4 + 1] = co[1] | ||||
|         pdata[i4 + 2] = co[2] | ||||
|         pdata[i4 + 3] = normal[2] * scale_pos # Cancel scale | ||||
|         ndata[i2    ] = normal[0] | ||||
|         ndata[i2 + 1] = normal[1] | ||||
|         if has_tex: | ||||
|             uv = v.uvs[t0map] | ||||
|             t0data[i2    ] = uv[0] | ||||
|             t0data[i2 + 1] = 1.0 - uv[1] # Reverse Y | ||||
|             if has_tex1: | ||||
|                 uv = v.uvs[t1map] | ||||
|                 t1data[i2    ] = uv[0] | ||||
|                 t1data[i2 + 1] = 1.0 - uv[1] | ||||
|         if has_morph_target: | ||||
|                uv = v.uvs[morph_uv_index] | ||||
|                morph_data[i2    ] = uv[0] | ||||
|                morph_data[i2 + 1] = 1.0 - uv[1] | ||||
|         if has_col: | ||||
|             i3 = i * 3 | ||||
|             cdata[i3    ] = v.col[0] | ||||
|             cdata[i3 + 1] = v.col[1] | ||||
|             cdata[i3 + 2] = v.col[2] | ||||
|  | ||||
|     # Indices | ||||
|     # Create dict for every material slot | ||||
|     prims = {ma.name if ma else '': [] for ma in export_mesh.materials} | ||||
|     v_maps = {ma.name if ma else '': [] for ma in export_mesh.materials} | ||||
|     if not prims: | ||||
|         # No materials | ||||
|         prims = {'': []} | ||||
|         v_maps = {'': []} | ||||
|  | ||||
|     # Create dict of {loop_indices : vertex} with each loop_index in each vertex in Vertex_list | ||||
|     vert_dict = {i : v for v in vert_list for i in v.loop_indices} | ||||
|     # For each polygon in a mesh | ||||
|     for poly in export_mesh.polygons: | ||||
|         # Index of the first loop of this polygon | ||||
|         first = poly.loop_start | ||||
|         # No materials assigned | ||||
|         if len(export_mesh.materials) == 0: | ||||
|             # Get prim | ||||
|             prim = prims[''] | ||||
|             v_map = v_maps[''] | ||||
|         else: | ||||
|             # First material | ||||
|             mat = export_mesh.materials[min(poly.material_index, len(export_mesh.materials) - 1)] | ||||
|             # Get prim for this material | ||||
|             prim = prims[mat.name if mat else ''] | ||||
|             v_map = v_maps[mat.name if mat else ''] | ||||
|         # List of indices for each loop_index belonging to this polygon | ||||
|         indices = [vert_dict[i].index for i in range(first, first+poly.loop_total)] | ||||
|         v_indices = [vert_dict[i].vertex_index for i in range(first, first+poly.loop_total)] | ||||
|  | ||||
|         # If 3 loops per polygon (Triangle?) | ||||
|         if poly.loop_total == 3: | ||||
|             prim += indices | ||||
|             v_map += v_indices | ||||
|         # If > 3 loops per polygon (Non-Triangular?) | ||||
|         elif poly.loop_total > 3: | ||||
|             for i in range(poly.loop_total-2): | ||||
|                 prim += (indices[-1], indices[i], indices[i + 1]) | ||||
|                 v_map += (v_indices[-1], v_indices[i], v_indices[i + 1]) | ||||
|  | ||||
|     # Write indices | ||||
|     o['index_arrays'] = [] | ||||
|     for mat, prim in prims.items(): | ||||
|         idata = [0] * len(prim) | ||||
|         v_map_data = [0] * len(prim) | ||||
|         v_map_sub = v_maps[mat] | ||||
|         for i, v in enumerate(prim): | ||||
|             idata[i] = v | ||||
|             v_map_data[i] = v_map_sub[i] | ||||
|         if len(idata) == 0: # No face assigned | ||||
|             continue | ||||
|         ia = {'values': idata, 'material': 0, 'vertex_map': v_map_data} | ||||
|         # Find material index for multi-mat mesh | ||||
|         if len(export_mesh.materials) > 1: | ||||
|             for i in range(0, len(export_mesh.materials)): | ||||
|                 if (export_mesh.materials[i] is not None and mat == export_mesh.materials[i].name) or \ | ||||
|                    (export_mesh.materials[i] is None and mat == ''):  # Default material for empty slots | ||||
|                     ia['material'] = i | ||||
|                     break | ||||
|         o['index_arrays'].append(ia) | ||||
|  | ||||
|     if has_tang: | ||||
|         tangdata = calc_tangents(pdata, ndata, t0data, o['index_arrays'], scale_pos) | ||||
|  | ||||
|     pdata *= invscale_pos | ||||
|     ndata *= 32767 | ||||
|     pdata = np.array(pdata, dtype='<i2') | ||||
|     ndata = np.array(ndata, dtype='<i2') | ||||
|     if has_tex: | ||||
|         t0data *= invscale_tex | ||||
|         t0data = np.array(t0data, dtype='<i2') | ||||
|         if has_tex1: | ||||
|             t1data *= invscale_tex | ||||
|             t1data = np.array(t1data, dtype='<i2') | ||||
|     if has_morph_target: | ||||
|         morph_data *= invscale_tex | ||||
|         morph_data = np.array(morph_data, dtype='<i2') | ||||
|     if has_col: | ||||
|         cdata *= 32767 | ||||
|         cdata = np.array(cdata, dtype='<i2') | ||||
|     if has_tang: | ||||
|         tangdata *= 32767 | ||||
|         tangdata = np.array(tangdata, dtype='<i2') | ||||
|  | ||||
|     # Output | ||||
|     o['vertex_arrays'] = [] | ||||
|     o['vertex_arrays'].append({ 'attrib': 'pos', 'values': pdata, 'data': 'short4norm' }) | ||||
|     o['vertex_arrays'].append({ 'attrib': 'nor', 'values': ndata, 'data': 'short2norm' }) | ||||
|     if has_tex: | ||||
|         o['vertex_arrays'].append({ 'attrib': 'tex', 'values': t0data, 'data': 'short2norm' }) | ||||
|         if has_tex1: | ||||
|             o['vertex_arrays'].append({ 'attrib': 'tex1', 'values': t1data, 'data': 'short2norm' }) | ||||
|     if has_morph_target: | ||||
|         o['vertex_arrays'].append({ 'attrib': 'morph', 'values': morph_data, 'data': 'short2norm' }) | ||||
|     if has_col: | ||||
|         o['vertex_arrays'].append({ 'attrib': 'col', 'values': cdata, 'data': 'short4norm', 'padding': 1 }) | ||||
|     if has_tang: | ||||
|         o['vertex_arrays'].append({ 'attrib': 'tang', 'values': tangdata, 'data': 'short4norm', 'padding': 1 }) | ||||
|  | ||||
|     return vert_list | ||||
|  | ||||
| def export_skin(self, bobject, armature, vert_list, o): | ||||
|     # This function exports all skinning data, which includes the skeleton | ||||
|     # and per-vertex bone influence data | ||||
|     oskin = {} | ||||
|     o['skin'] = oskin | ||||
|  | ||||
|     # Write the skin bind pose transform | ||||
|     otrans = {} | ||||
|     oskin['transform'] = otrans | ||||
|     otrans['values'] = self.write_matrix(bobject.matrix_world) | ||||
|  | ||||
|     # Write the bone object reference array | ||||
|     oskin['bone_ref_array'] = [] | ||||
|     oskin['bone_len_array'] = [] | ||||
|  | ||||
|     bone_array = armature.data.bones | ||||
|     bone_count = len(bone_array) | ||||
|     rpdat = lnx.utils.get_rp() | ||||
|     max_bones = rpdat.lnx_skin_max_bones | ||||
|     if bone_count > max_bones: | ||||
|         log.warn(bobject.name + ' - ' + str(bone_count) + ' bones found, exceeds maximum of ' + str(max_bones) + ' bones defined - raise the value in Camera Data - Leenkx Render Props - Max Bones') | ||||
|  | ||||
|     for i in range(bone_count): | ||||
|         boneRef = self.find_bone(bone_array[i].name) | ||||
|         if boneRef: | ||||
|             oskin['bone_ref_array'].append(boneRef[1]["structName"]) | ||||
|             oskin['bone_len_array'].append(bone_array[i].length) | ||||
|         else: | ||||
|             oskin['bone_ref_array'].append("") | ||||
|             oskin['bone_len_array'].append(0.0) | ||||
|  | ||||
|     # Write the bind pose transform array | ||||
|     oskin['transformsI'] = [] | ||||
|     for i in range(bone_count): | ||||
|         skeletonI = (armature.matrix_world @ bone_array[i].matrix_local).inverted_safe() | ||||
|         skeletonI = (skeletonI @ bobject.matrix_world) | ||||
|         oskin['transformsI'].append(self.write_matrix(skeletonI)) | ||||
|  | ||||
|     # Export the per-vertex bone influence data | ||||
|     group_remap = [] | ||||
|     for group in bobject.vertex_groups: | ||||
|         for i in range(bone_count): | ||||
|             if bone_array[i].name == group.name: | ||||
|                 group_remap.append(i) | ||||
|                 break | ||||
|         else: | ||||
|             group_remap.append(-1) | ||||
|  | ||||
|     bone_count_array = np.empty(len(vert_list), dtype='<i2') | ||||
|     bone_index_array = np.empty(len(vert_list) * 4, dtype='<i2') | ||||
|     bone_weight_array = np.empty(len(vert_list) * 4, dtype='<i2') | ||||
|  | ||||
|     vertices = bobject.data.vertices | ||||
|     count = 0 | ||||
|     for index, v in enumerate(vert_list): | ||||
|         bone_count = 0 | ||||
|         total_weight = 0.0 | ||||
|         bone_values = [] | ||||
|         for g in vertices[v.vertex_index].groups: | ||||
|             bone_index = group_remap[g.group] | ||||
|             bone_weight = g.weight | ||||
|             if bone_index >= 0: #and bone_weight != 0.0: | ||||
|                 bone_values.append((bone_weight, bone_index)) | ||||
|                 total_weight += bone_weight | ||||
|                 bone_count += 1 | ||||
|  | ||||
|         if bone_count > 4: | ||||
|             bone_count = 4 | ||||
|             bone_values.sort(reverse=True) | ||||
|             bone_values = bone_values[:4] | ||||
|  | ||||
|         bone_count_array[index] = bone_count | ||||
|         for bv in bone_values: | ||||
|             bone_weight_array[count] = bv[0] * 32767 | ||||
|             bone_index_array[count] = bv[1] | ||||
|             count += 1 | ||||
|          | ||||
|         if total_weight not in (0.0, 1.0): | ||||
|             normalizer = 1.0 / total_weight | ||||
|             for i in range(bone_count): | ||||
|                 bone_weight_array[count - i - 1] *= normalizer | ||||
|  | ||||
|     oskin['bone_count_array'] = bone_count_array | ||||
|     oskin['bone_index_array'] = bone_index_array[:count] | ||||
|     oskin['bone_weight_array'] = bone_weight_array[:count] | ||||
|  | ||||
|     # Bone constraints | ||||
|     for bone in armature.pose.bones: | ||||
|         if len(bone.constraints) > 0: | ||||
|             if 'constraints' not in oskin: | ||||
|                 oskin['constraints'] = [] | ||||
|             self.add_constraints(bone, oskin, bone=True) | ||||
		Reference in New Issue
	
	Block a user