def __gather_skin(blender_object, export_settings): modifiers = {m.type: m for m in blender_object.modifiers} if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None: return None # no skin needed when the modifier is linked without having a vertex group vertex_groups = blender_object.vertex_groups if len(vertex_groups) == 0: return None # check if any vertices in the mesh are part of a vertex group depsgraph = bpy.context.evaluated_depsgraph_get() blender_mesh_owner = blender_object.evaluated_get(depsgraph) blender_mesh = blender_mesh_owner.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph) if not any(vertex.groups is not None and len(vertex.groups) > 0 for vertex in blender_mesh.vertices): return None # Prevent infinite recursive error. A mesh can't have an Armature modifier # and be bone parented to a bone of this armature # In that case, ignore the armature modifier, keep only the bone parenting if blender_object.parent is not None \ and blender_object.parent_type == 'BONE' \ and blender_object.parent.name == modifiers["ARMATURE"].object.name: return None # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, export_settings)
def __gather_skin(blender_object, export_settings): modifiers = {m.type: m for m in blender_object.modifiers} if "ARMATURE" in modifiers: # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures return gltf2_blender_gather_skins.gather_skin( modifiers["ARMATURE"].object, export_settings)
def __gather_skin(blender_object, export_settings): modifiers = {m.type: m for m in blender_object.modifiers} if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None: return None # no skin needed when the modifier is linked without having a vertex group vertex_groups = blender_object.vertex_groups if len(vertex_groups) == 0: return None # check if any vertices in the mesh are part of a vertex group if bpy.app.version < (2, 80, 0): blender_mesh = blender_object.to_mesh(bpy.context.scene, True, 'PREVIEW') else: depsgraph = bpy.context.evaluated_depsgraph_get() blender_mesh_owner = blender_object.evaluated_get(depsgraph) blender_mesh = blender_mesh_owner.to_mesh( preserve_all_data_layers=True, depsgraph=depsgraph) if not any(vertex.groups is not None and len(vertex.groups) > 0 for vertex in blender_mesh.vertices): return None # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, blender_object, export_settings)
def __gather_skin(blender_object, export_settings): modifiers = {m.type: m for m in blender_object.modifiers} if "ARMATURE" not in modifiers or modifiers["ARMATURE"].object is None: return None # no skin needed when the modifier is linked without having a vertex group vertex_groups = blender_object.vertex_groups if len(vertex_groups) == 0: return None # check if any vertices in the mesh are part of a vertex group blender_mesh = blender_object.to_mesh(bpy.context.depsgraph, True) if not any(vertex.groups is not None and len(vertex.groups) > 0 for vertex in blender_mesh.vertices): return None # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures return gltf2_blender_gather_skins.gather_skin(modifiers["ARMATURE"].object, blender_object, export_settings)
def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, export_settings): """ Extract primitives from a mesh. Polygons are triangulated and sorted by material. Furthermore, primitives are split up, if the indices range is exceeded. Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals. """ print_console('INFO', 'Extracting primitive') use_tangents = False if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: try: blender_mesh.calc_tangents() use_tangents = True except Exception: print_console( 'WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.' ) # material_map = {} # # Gathering position, normal and tex_coords. # no_material_attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []} if use_tangents: no_material_attributes[TANGENT_ATTRIBUTE] = [] # # Directory of materials with its primitive. # no_material_primitives = { MATERIAL_ID: '', INDICES_ID: [], ATTRIBUTES_ID: no_material_attributes } material_name_to_primitives = {'': no_material_primitives} # vertex_index_to_new_indices = {} material_map[''] = vertex_index_to_new_indices # # Create primitive for each material. # for blender_material in blender_mesh.materials: if blender_material is None: continue attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []} if use_tangents: attributes[TANGENT_ATTRIBUTE] = [] primitive = { MATERIAL_ID: blender_material.name, INDICES_ID: [], ATTRIBUTES_ID: attributes } material_name_to_primitives[blender_material.name] = primitive # vertex_index_to_new_indices = {} material_map[blender_material.name] = vertex_index_to_new_indices tex_coord_max = 0 if blender_mesh.uv_layers.active: tex_coord_max = len(blender_mesh.uv_layers) # vertex_colors = {} color_index = 0 for vertex_color in blender_mesh.vertex_colors: vertex_color_name = COLOR_PREFIX + str(color_index) vertex_colors[vertex_color_name] = vertex_color color_index += 1 if color_index >= GLTF_MAX_COLORS: break color_max = color_index # bone_max = 0 for blender_polygon in blender_mesh.polygons: for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[loop_index].vertex_index bones_count = len(blender_mesh.vertices[vertex_index].groups) if bones_count > 0: if bones_count % 4 == 0: bones_count -= 1 bone_max = max(bone_max, bones_count // 4 + 1) # morph_max = 0 blender_shape_keys = [] if blender_mesh.shape_keys is not None: morph_max = len(blender_mesh.shape_keys.key_blocks) - 1 for blender_shape_key in blender_mesh.shape_keys.key_blocks: if blender_shape_key != blender_shape_key.relative_key: blender_shape_keys.append( ShapeKey( blender_shape_key, blender_shape_key.normals_vertex_get( ), # calculate vertex normals for this shape key blender_shape_key.normals_polygon_get()) ) # calculate polygon normals for this shape key # # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. # for blender_polygon in blender_mesh.polygons: export_color = True # if blender_polygon.material_index < 0 or blender_polygon.material_index >= len(blender_mesh.materials) or \ blender_mesh.materials[blender_polygon.material_index] is None: primitive = material_name_to_primitives[''] vertex_index_to_new_indices = material_map[''] else: primitive = material_name_to_primitives[blender_mesh.materials[ blender_polygon.material_index].name] vertex_index_to_new_indices = material_map[blender_mesh.materials[ blender_polygon.material_index].name] # attributes = primitive[ATTRIBUTES_ID] face_normal = blender_polygon.normal face_tangent = Vector((0.0, 0.0, 0.0)) face_bitangent = Vector((0.0, 0.0, 0.0)) if use_tangents: for loop_index in blender_polygon.loop_indices: temp_vertex = blender_mesh.loops[loop_index] face_tangent += temp_vertex.tangent face_bitangent += temp_vertex.bitangent face_tangent.normalize() face_bitangent.normalize() # indices = primitive[INDICES_ID] loop_index_list = [] if len(blender_polygon.loop_indices) == 3: loop_index_list.extend(blender_polygon.loop_indices) elif len(blender_polygon.loop_indices) > 3: # Triangulation of polygon. Using internal function, as non-convex polygons could exist. polyline = [] for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[loop_index].vertex_index v = blender_mesh.vertices[vertex_index].co polyline.append(Vector((v[0], v[1], v[2]))) triangles = tessellate_polygon((polyline, )) for triangle in triangles: loop_index_list.append( blender_polygon.loop_indices[triangle[0]]) loop_index_list.append( blender_polygon.loop_indices[triangle[2]]) loop_index_list.append( blender_polygon.loop_indices[triangle[1]]) else: continue for loop_index in loop_index_list: vertex_index = blender_mesh.loops[loop_index].vertex_index if vertex_index_to_new_indices.get(vertex_index) is None: vertex_index_to_new_indices[vertex_index] = [] # v = None n = None t = None b = None uvs = [] colors = [] joints = [] weights = [] target_positions = [] target_normals = [] target_tangents = [] vertex = blender_mesh.vertices[vertex_index] v = convert_swizzle_location(vertex.co, export_settings) if blender_polygon.use_smooth: n = convert_swizzle_location(vertex.normal, export_settings) if use_tangents: t = convert_swizzle_tangent( blender_mesh.loops[loop_index].tangent, export_settings) b = convert_swizzle_location( blender_mesh.loops[loop_index].bitangent, export_settings) else: n = convert_swizzle_location(face_normal, export_settings) if use_tangents: t = convert_swizzle_tangent(face_tangent, export_settings) b = convert_swizzle_location(face_bitangent, export_settings) if use_tangents: tv = Vector((t[0], t[1], t[2])) bv = Vector((b[0], b[1], b[2])) nv = Vector((n[0], n[1], n[2])) if (nv.cross(tv)).dot(bv) < 0.0: t[3] = -1.0 if blender_mesh.uv_layers.active: for tex_coord_index in range(0, tex_coord_max): uv = blender_mesh.uv_layers[tex_coord_index].data[ loop_index].uv uvs.append([uv.x, 1.0 - uv.y]) # if color_max > 0 and export_color: for color_index in range(0, color_max): color_name = COLOR_PREFIX + str(color_index) color = vertex_colors[color_name].data[loop_index].color colors.append([ color_srgb_to_scene_linear(color[0]), color_srgb_to_scene_linear(color[1]), color_srgb_to_scene_linear(color[2]), 1.0 ]) # bone_count = 0 if blender_vertex_groups is not None and vertex.groups is not None and len( vertex.groups) > 0 and export_settings[ gltf2_blender_export_keys.SKINS]: joint = [] weight = [] for group_element in vertex.groups: if len(joint) == 4: bone_count += 1 joints.append(joint) weights.append(weight) joint = [] weight = [] # joint_weight = group_element.weight if joint_weight <= 0.0: continue # vertex_group_index = group_element.group vertex_group_name = blender_vertex_groups[ vertex_group_index].name joint_index = None if modifiers is not None: modifiers_dict = {m.type: m for m in modifiers} if "ARMATURE" in modifiers_dict: armature = modifiers_dict["ARMATURE"].object skin = gltf2_blender_gather_skins.gather_skin( armature, export_settings) for index, j in enumerate(skin.joints): if j.name == vertex_group_name: joint_index = index break # if joint_index is not None: joint.append(joint_index) weight.append(joint_weight) if len(joint) > 0: bone_count += 1 for fill in range(0, 4 - len(joint)): joint.append(0) weight.append(0.0) joints.append(joint) weights.append(weight) for fill in range(0, bone_max - bone_count): joints.append([0, 0, 0, 0]) weights.append([0.0, 0.0, 0.0, 0.0]) # if morph_max > 0 and export_settings[ gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): blender_shape_key = blender_shape_keys[morph_index] v_morph = convert_swizzle_location( blender_shape_key.shape_key.data[vertex_index].co, export_settings) # Store delta. v_morph -= v target_positions.append(v_morph) # n_morph = None if blender_polygon.use_smooth: temp_normals = blender_shape_key.vertex_normals n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1], temp_normals[vertex_index * 3 + 2]) else: temp_normals = blender_shape_key.polygon_normals n_morph = (temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], temp_normals[blender_polygon.index * 3 + 2]) n_morph = convert_swizzle_location(n_morph, export_settings) # Store delta. n_morph -= n target_normals.append(n_morph) # if use_tangents: rotation = n_morph.rotation_difference(n) t_morph = Vector((t[0], t[1], t[2])) t_morph.rotate(rotation) target_tangents.append(t_morph) # # create = True for current_new_index in vertex_index_to_new_indices[vertex_index]: found = True for i in range(0, 3): if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]: found = False break if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]: found = False break if use_tangents: for i in range(0, 4): if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]: found = False break if not found: continue for tex_coord_index in range(0, tex_coord_max): uv = uvs[tex_coord_index] tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) for i in range(0, 2): if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]: found = False break if export_color: for color_index in range(0, color_max): color = colors[color_index] color_id = COLOR_PREFIX + str(color_index) for i in range(0, 3): # Alpha is always 1.0 - see above. current_color = attributes[color_id][ current_new_index * 4 + i] if color_srgb_to_scene_linear( current_color) != color[i]: found = False break if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joint = joints[bone_index] weight = weights[bone_index] joint_id = JOINTS_PREFIX + str(bone_index) weight_id = WEIGHTS_PREFIX + str(bone_index) for i in range(0, 4): if attributes[joint_id][current_new_index * 4 + i] != joint[i]: found = False break if attributes[weight_id][current_new_index * 4 + i] != weight[i]: found = False break if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_position = target_positions[morph_index] target_normal = target_normals[morph_index] if use_tangents: target_tangent = target_tangents[morph_index] target_position_id = MORPH_POSITION_PREFIX + str( morph_index) target_normal_id = MORPH_NORMAL_PREFIX + str( morph_index) target_tangent_id = MORPH_TANGENT_PREFIX + str( morph_index) for i in range(0, 3): if attributes[target_position_id][ current_new_index * 3 + i] != target_position[i]: found = False break if attributes[target_normal_id][ current_new_index * 3 + i] != target_normal[i]: found = False break if use_tangents: if attributes[target_tangent_id][ current_new_index * 3 + i] != target_tangent[i]: found = False break if found: indices.append(current_new_index) create = False break if not create: continue new_index = 0 if primitive.get('max_index') is not None: new_index = primitive['max_index'] + 1 primitive['max_index'] = new_index vertex_index_to_new_indices[vertex_index].append(new_index) # # indices.append(new_index) # attributes[POSITION_ATTRIBUTE].extend(v) attributes[NORMAL_ATTRIBUTE].extend(n) if use_tangents: attributes[TANGENT_ATTRIBUTE].extend(t) if blender_mesh.uv_layers.active: for tex_coord_index in range(0, tex_coord_max): tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) if attributes.get(tex_coord_id) is None: attributes[tex_coord_id] = [] attributes[tex_coord_id].extend(uvs[tex_coord_index]) if export_color: for color_index in range(0, color_max): color_id = COLOR_PREFIX + str(color_index) if attributes.get(color_id) is None: attributes[color_id] = [] attributes[color_id].extend(colors[color_index]) if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joint_id = JOINTS_PREFIX + str(bone_index) if attributes.get(joint_id) is None: attributes[joint_id] = [] attributes[joint_id].extend(joints[bone_index]) weight_id = WEIGHTS_PREFIX + str(bone_index) if attributes.get(weight_id) is None: attributes[weight_id] = [] attributes[weight_id].extend(weights[bone_index]) if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_position_id = MORPH_POSITION_PREFIX + str( morph_index) if attributes.get(target_position_id) is None: attributes[target_position_id] = [] attributes[target_position_id].extend( target_positions[morph_index]) target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) if attributes.get(target_normal_id) is None: attributes[target_normal_id] = [] attributes[target_normal_id].extend( target_normals[morph_index]) if use_tangents: target_tangent_id = MORPH_TANGENT_PREFIX + str( morph_index) if attributes.get(target_tangent_id) is None: attributes[target_tangent_id] = [] attributes[target_tangent_id].extend( target_tangents[morph_index]) # # Add primitive plus split them if needed. # result_primitives = [] for material_name, primitive in material_name_to_primitives.items(): export_color = True # indices = primitive[INDICES_ID] if len(indices) == 0: continue position = primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE] normal = primitive[ATTRIBUTES_ID][NORMAL_ATTRIBUTE] if use_tangents: tangent = primitive[ATTRIBUTES_ID][TANGENT_ATTRIBUTE] tex_coords = [] for tex_coord_index in range(0, tex_coord_max): tex_coords.append(primitive[ATTRIBUTES_ID][TEXCOORD_PREFIX + str(tex_coord_index)]) colors = [] if export_color: for color_index in range(0, color_max): tex_coords.append(primitive[ATTRIBUTES_ID][COLOR_PREFIX + str(color_index)]) joints = [] weights = [] if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joints.append(primitive[ATTRIBUTES_ID][JOINTS_PREFIX + str(bone_index)]) weights.append(primitive[ATTRIBUTES_ID][WEIGHTS_PREFIX + str(bone_index)]) target_positions = [] target_normals = [] target_tangents = [] if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_positions.append( primitive[ATTRIBUTES_ID][MORPH_POSITION_PREFIX + str(morph_index)]) target_normals.append( primitive[ATTRIBUTES_ID][MORPH_NORMAL_PREFIX + str(morph_index)]) if use_tangents: target_tangents.append( primitive[ATTRIBUTES_ID][MORPH_TANGENT_PREFIX + str(morph_index)]) # count = len(indices) if count == 0: continue max_index = max(indices) # # NOTE: Values used by some graphics APIs as "primitive restart" values are disallowed. # Specifically, the value 65535 (in UINT16) cannot be used as a vertex index. # https://github.com/KhronosGroup/glTF/issues/1142 # https://github.com/KhronosGroup/glTF/pull/1476/files range_indices = 65535 # if max_index >= range_indices: # # Splitting result_primitives. # # At start, all indices are pending. pending_attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []} if use_tangents: pending_attributes[TANGENT_ATTRIBUTE] = [] pending_primitive = { MATERIAL_ID: material_name, INDICES_ID: [], ATTRIBUTES_ID: pending_attributes } pending_primitive[INDICES_ID].extend(indices) pending_attributes[POSITION_ATTRIBUTE].extend(position) pending_attributes[NORMAL_ATTRIBUTE].extend(normal) if use_tangents: pending_attributes[TANGENT_ATTRIBUTE].extend(tangent) tex_coord_index = 0 for tex_coord in tex_coords: pending_attributes[TEXCOORD_PREFIX + str(tex_coord_index)] = tex_coord tex_coord_index += 1 if export_color: color_index = 0 for color in colors: pending_attributes[COLOR_PREFIX + str(color_index)] = color color_index += 1 if export_settings[gltf2_blender_export_keys.SKINS]: joint_index = 0 for joint in joints: pending_attributes[JOINTS_PREFIX + str(joint_index)] = joint joint_index += 1 weight_index = 0 for weight in weights: pending_attributes[WEIGHTS_PREFIX + str(weight_index)] = weight weight_index += 1 if export_settings[gltf2_blender_export_keys.MORPH]: morph_index = 0 for target_position in target_positions: pending_attributes[MORPH_POSITION_PREFIX + str(morph_index)] = target_position morph_index += 1 morph_index = 0 for target_normal in target_normals: pending_attributes[MORPH_NORMAL_PREFIX + str(morph_index)] = target_normal morph_index += 1 if use_tangents: morph_index = 0 for target_tangent in target_tangents: pending_attributes[MORPH_TANGENT_PREFIX + str(morph_index)] = target_tangent morph_index += 1 pending_indices = pending_primitive[INDICES_ID] # Continue until all are processed. while len(pending_indices) > 0: process_indices = pending_primitive[INDICES_ID] max_index = max(process_indices) pending_indices = [] # # all_local_indices = [] for i in range(0, (max_index // range_indices) + 1): all_local_indices.append([]) # # # For all faces ... for face_index in range(0, len(process_indices), 3): written = False face_min_index = min(process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2]) face_max_index = max(process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2]) # ... check if it can be but in a range of maximum indices. for i in range(0, (max_index // range_indices) + 1): offset = i * range_indices # Yes, so store the primitive with its indices. if face_min_index >= offset and face_max_index < offset + range_indices: all_local_indices[i].extend([ process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2] ]) written = True break # If not written, the triangle face has indices from different ranges. if not written: pending_indices.extend([ process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2] ]) # Only add result_primitives, which do have indices in it. for local_indices in all_local_indices: if len(local_indices) > 0: current_primitive = extract_primitive_floor( pending_primitive, local_indices, use_tangents) result_primitives.append(current_primitive) print_console( 'DEBUG', 'Adding primitive with splitting. Indices: ' + str(len(current_primitive[INDICES_ID])) + ' Vertices: ' + str( len(current_primitive[ATTRIBUTES_ID] [POSITION_ATTRIBUTE]) // 3)) # Process primitive faces having indices in several ranges. if len(pending_indices) > 0: pending_primitive = extract_primitive_pack( pending_primitive, pending_indices, use_tangents) print_console( 'DEBUG', 'Creating temporary primitive for splitting') else: # # No splitting needed. # result_primitives.append(primitive) print_console( 'DEBUG', 'Adding primitive without splitting. Indices: ' + str(len(primitive[INDICES_ID])) + ' Vertices: ' + str(len(primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3)) print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) return result_primitives
def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vertex_groups, modifiers, export_settings): """Extract primitives from a mesh.""" print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) use_normals = export_settings[gltf2_blender_export_keys.NORMALS] if use_normals: blender_mesh.calc_normals_split() use_tangents = False if use_normals and export_settings[gltf2_blender_export_keys.TANGENTS]: if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: try: blender_mesh.calc_tangents() use_tangents = True except Exception: print_console( 'WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.' ) tex_coord_max = 0 if export_settings[gltf2_blender_export_keys.TEX_COORDS]: if blender_mesh.uv_layers.active: tex_coord_max = len(blender_mesh.uv_layers) color_max = 0 if export_settings[gltf2_blender_export_keys.COLORS]: color_max = len(blender_mesh.vertex_colors) armature = None skin = None if blender_vertex_groups and export_settings[ gltf2_blender_export_keys.SKINS]: if modifiers is not None: modifiers_dict = {m.type: m for m in modifiers} if "ARMATURE" in modifiers_dict: modifier = modifiers_dict["ARMATURE"] armature = modifier.object # Skin must be ignored if the object is parented to a bone of the armature # (This creates an infinite recursive error) # So ignoring skin in that case is_child_of_arma = (armature and blender_object and blender_object.parent_type == "BONE" and blender_object.parent.name == armature.name) if is_child_of_arma: armature = None if armature: skin = gltf2_blender_gather_skins.gather_skin( armature, export_settings) if not skin: armature = None use_morph_normals = use_normals and export_settings[ gltf2_blender_export_keys.MORPH_NORMAL] use_morph_tangents = use_morph_normals and use_tangents and export_settings[ gltf2_blender_export_keys.MORPH_TANGENT] key_blocks = [] if blender_mesh.shape_keys and export_settings[ gltf2_blender_export_keys.MORPH]: key_blocks = [ key_block for key_block in blender_mesh.shape_keys.key_blocks if not (key_block == key_block.relative_key or key_block.mute) ] use_materials = export_settings[gltf2_blender_export_keys.MATERIALS] # Fetch vert positions and bone data (joint,weights) locs, morph_locs = __get_positions(blender_mesh, key_blocks, armature, blender_object, export_settings) if skin: vert_bones, num_joint_sets = __get_bone_data(blender_mesh, skin, blender_vertex_groups) # In Blender there is both per-vert data, like position, and also per-loop # (loop=corner-of-poly) data, like normals or UVs. glTF only has per-vert # data, so we need to split Blender verts up into potentially-multiple glTF # verts. # # First, we'll collect a "dot" for every loop: a struct that stores all the # attributes at that loop, namely the vertex index (which determines all # per-vert data), and all the per-loop data like UVs, etc. # # Each unique dot will become one unique glTF vert. # List all fields the dot struct needs. dot_fields = [('vertex_index', np.uint32)] if use_normals: dot_fields += [('nx', np.float32), ('ny', np.float32), ('nz', np.float32)] if use_tangents: dot_fields += [('tx', np.float32), ('ty', np.float32), ('tz', np.float32), ('tw', np.float32)] for uv_i in range(tex_coord_max): dot_fields += [('uv%dx' % uv_i, np.float32), ('uv%dy' % uv_i, np.float32)] for col_i in range(color_max): dot_fields += [ ('color%dr' % col_i, np.float32), ('color%dg' % col_i, np.float32), ('color%db' % col_i, np.float32), ('color%da' % col_i, np.float32), ] if use_morph_normals: for morph_i, _ in enumerate(key_blocks): dot_fields += [ ('morph%dnx' % morph_i, np.float32), ('morph%dny' % morph_i, np.float32), ('morph%dnz' % morph_i, np.float32), ] dots = np.empty(len(blender_mesh.loops), dtype=np.dtype(dot_fields)) vidxs = np.empty(len(blender_mesh.loops)) blender_mesh.loops.foreach_get('vertex_index', vidxs) dots['vertex_index'] = vidxs del vidxs if use_normals: kbs = key_blocks if use_morph_normals else [] normals, morph_normals = __get_normals(blender_mesh, kbs, armature, blender_object, export_settings) dots['nx'] = normals[:, 0] dots['ny'] = normals[:, 1] dots['nz'] = normals[:, 2] del normals for morph_i, ns in enumerate(morph_normals): dots['morph%dnx' % morph_i] = ns[:, 0] dots['morph%dny' % morph_i] = ns[:, 1] dots['morph%dnz' % morph_i] = ns[:, 2] del morph_normals if use_tangents: tangents = __get_tangents(blender_mesh, armature, blender_object, export_settings) dots['tx'] = tangents[:, 0] dots['ty'] = tangents[:, 1] dots['tz'] = tangents[:, 2] del tangents signs = __get_bitangent_signs(blender_mesh, armature, blender_object, export_settings) dots['tw'] = signs del signs for uv_i in range(tex_coord_max): uvs = __get_uvs(blender_mesh, uv_i) dots['uv%dx' % uv_i] = uvs[:, 0] dots['uv%dy' % uv_i] = uvs[:, 1] del uvs for col_i in range(color_max): colors = __get_colors(blender_mesh, col_i) dots['color%dr' % col_i] = colors[:, 0] dots['color%dg' % col_i] = colors[:, 1] dots['color%db' % col_i] = colors[:, 2] dots['color%da' % col_i] = colors[:, 3] del colors # Calculate triangles and sort them into primitives. blender_mesh.calc_loop_triangles() loop_indices = np.empty(len(blender_mesh.loop_triangles) * 3, dtype=np.uint32) blender_mesh.loop_triangles.foreach_get('loops', loop_indices) prim_indices = { } # maps material index to TRIANGLES-style indices into dots if use_materials == "NONE": # Only for None. For placeholder and export, keep primitives # Put all vertices into one primitive prim_indices[-1] = loop_indices else: # Bucket by material index. tri_material_idxs = np.empty(len(blender_mesh.loop_triangles), dtype=np.uint32) blender_mesh.loop_triangles.foreach_get('material_index', tri_material_idxs) loop_material_idxs = np.repeat(tri_material_idxs, 3) # material index for every loop unique_material_idxs = np.unique(tri_material_idxs) del tri_material_idxs for material_idx in unique_material_idxs: prim_indices[material_idx] = loop_indices[loop_material_idxs == material_idx] # Create all the primitives. primitives = [] for material_idx, dot_indices in prim_indices.items(): # Extract just dots used by this primitive, deduplicate them, and # calculate indices into this deduplicated list. prim_dots = dots[dot_indices] prim_dots, indices = np.unique(prim_dots, return_inverse=True) if len(prim_dots) == 0: continue # Now just move all the data for prim_dots into attribute arrays attributes = {} blender_idxs = prim_dots['vertex_index'] attributes['POSITION'] = locs[blender_idxs] for morph_i, vs in enumerate(morph_locs): attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] if use_normals: normals = np.empty((len(prim_dots), 3), dtype=np.float32) normals[:, 0] = prim_dots['nx'] normals[:, 1] = prim_dots['ny'] normals[:, 2] = prim_dots['nz'] attributes['NORMAL'] = normals if use_tangents: tangents = np.empty((len(prim_dots), 4), dtype=np.float32) tangents[:, 0] = prim_dots['tx'] tangents[:, 1] = prim_dots['ty'] tangents[:, 2] = prim_dots['tz'] tangents[:, 3] = prim_dots['tw'] attributes['TANGENT'] = tangents if use_morph_normals: for morph_i, _ in enumerate(key_blocks): ns = np.empty((len(prim_dots), 3), dtype=np.float32) ns[:, 0] = prim_dots['morph%dnx' % morph_i] ns[:, 1] = prim_dots['morph%dny' % morph_i] ns[:, 2] = prim_dots['morph%dnz' % morph_i] attributes['MORPH_NORMAL_%d' % morph_i] = ns if use_morph_tangents: attributes['MORPH_TANGENT_%d' % morph_i] = __calc_morph_tangents( normals, ns, tangents) for tex_coord_i in range(tex_coord_max): uvs = np.empty((len(prim_dots), 2), dtype=np.float32) uvs[:, 0] = prim_dots['uv%dx' % tex_coord_i] uvs[:, 1] = prim_dots['uv%dy' % tex_coord_i] attributes['TEXCOORD_%d' % tex_coord_i] = uvs for color_i in range(color_max): colors = np.empty((len(prim_dots), 4), dtype=np.float32) colors[:, 0] = prim_dots['color%dr' % color_i] colors[:, 1] = prim_dots['color%dg' % color_i] colors[:, 2] = prim_dots['color%db' % color_i] colors[:, 3] = prim_dots['color%da' % color_i] attributes['COLOR_%d' % color_i] = colors if skin: joints = [[] for _ in range(num_joint_sets)] weights = [[] for _ in range(num_joint_sets)] for vi in blender_idxs: bones = vert_bones[vi] for j in range(0, 4 * num_joint_sets): if j < len(bones): joint, weight = bones[j] else: joint, weight = 0, 0.0 joints[j // 4].append(joint) weights[j // 4].append(weight) for i, (js, ws) in enumerate(zip(joints, weights)): attributes['JOINTS_%d' % i] = js attributes['WEIGHTS_%d' % i] = ws primitives.append({ 'attributes': attributes, 'indices': indices, 'material': material_idx, }) if export_settings['gltf_loose_edges']: # Find loose edges loose_edges = [e for e in blender_mesh.edges if e.is_loose] blender_idxs = [vi for e in loose_edges for vi in e.vertices] if blender_idxs: # Export one glTF vert per unique Blender vert in a loose edge blender_idxs = np.array(blender_idxs, dtype=np.uint32) blender_idxs, indices = np.unique(blender_idxs, return_inverse=True) attributes = {} attributes['POSITION'] = locs[blender_idxs] for morph_i, vs in enumerate(morph_locs): attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] if skin: joints = [[] for _ in range(num_joint_sets)] weights = [[] for _ in range(num_joint_sets)] for vi in blender_idxs: bones = vert_bones[vi] for j in range(0, 4 * num_joint_sets): if j < len(bones): joint, weight = bones[j] else: joint, weight = 0, 0.0 joints[j // 4].append(joint) weights[j // 4].append(weight) for i, (js, ws) in enumerate(zip(joints, weights)): attributes['JOINTS_%d' % i] = js attributes['WEIGHTS_%d' % i] = ws primitives.append({ 'attributes': attributes, 'indices': indices, 'mode': 1, # LINES 'material': 0, }) if export_settings['gltf_loose_points']: # Find loose points verts_in_edge = set(vi for e in blender_mesh.edges for vi in e.vertices) blender_idxs = [ vi for vi, _ in enumerate(blender_mesh.vertices) if vi not in verts_in_edge ] if blender_idxs: blender_idxs = np.array(blender_idxs, dtype=np.uint32) attributes = {} attributes['POSITION'] = locs[blender_idxs] for morph_i, vs in enumerate(morph_locs): attributes['MORPH_POSITION_%d' % morph_i] = vs[blender_idxs] if skin: joints = [[] for _ in range(num_joint_sets)] weights = [[] for _ in range(num_joint_sets)] for vi in blender_idxs: bones = vert_bones[vi] for j in range(0, 4 * num_joint_sets): if j < len(bones): joint, weight = bones[j] else: joint, weight = 0, 0.0 joints[j // 4].append(joint) weights[j // 4].append(weight) for i, (js, ws) in enumerate(zip(joints, weights)): attributes['JOINTS_%d' % i] = js attributes['WEIGHTS_%d' % i] = ws primitives.append({ 'attributes': attributes, 'mode': 0, # POINTS 'material': 0, }) print_console('INFO', 'Primitives created: %d' % len(primitives)) return primitives
def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vertex_groups, modifiers, export_settings): """ Extract primitives from a mesh. Polygons are triangulated and sorted by material. Furthermore, primitives are split up, if the indices range is exceeded. Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals. """ print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) if blender_mesh.has_custom_normals: # Custom normals are all (0, 0, 0) until calling calc_normals_split() or calc_tangents(). blender_mesh.calc_normals_split() use_tangents = False if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: try: blender_mesh.calc_tangents() use_tangents = True except Exception: print_console( 'WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.' ) # material_map = {} # # Gathering position, normal and tex_coords. # no_material_attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []} if use_tangents: no_material_attributes[TANGENT_ATTRIBUTE] = [] # # Directory of materials with its primitive. # no_material_primitives = { MATERIAL_ID: 0, INDICES_ID: [], ATTRIBUTES_ID: no_material_attributes } material_idx_to_primitives = {0: no_material_primitives} # vertex_index_to_new_indices = {} material_map[0] = vertex_index_to_new_indices # # Create primitive for each material. # for (mat_idx, _) in enumerate(blender_mesh.materials): attributes = {POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: []} if use_tangents: attributes[TANGENT_ATTRIBUTE] = [] primitive = { MATERIAL_ID: mat_idx, INDICES_ID: [], ATTRIBUTES_ID: attributes } material_idx_to_primitives[mat_idx] = primitive # vertex_index_to_new_indices = {} material_map[mat_idx] = vertex_index_to_new_indices tex_coord_max = 0 if blender_mesh.uv_layers.active: tex_coord_max = len(blender_mesh.uv_layers) # vertex_colors = {} color_index = 0 for vertex_color in blender_mesh.vertex_colors: vertex_color_name = COLOR_PREFIX + str(color_index) vertex_colors[vertex_color_name] = vertex_color color_index += 1 if color_index >= GLTF_MAX_COLORS: break color_max = color_index # bone_max = 0 for blender_polygon in blender_mesh.polygons: for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[loop_index].vertex_index bones_count = len(blender_mesh.vertices[vertex_index].groups) if bones_count > 0: if bones_count % 4 == 0: bones_count -= 1 bone_max = max(bone_max, bones_count // 4 + 1) # morph_max = 0 blender_shape_keys = [] if blender_mesh.shape_keys is not None: for blender_shape_key in blender_mesh.shape_keys.key_blocks: if blender_shape_key != blender_shape_key.relative_key: if blender_shape_key.mute is False: morph_max += 1 blender_shape_keys.append( ShapeKey( blender_shape_key, blender_shape_key.normals_vertex_get( ), # calculate vertex normals for this shape key blender_shape_key.normals_polygon_get()) ) # calculate polygon normals for this shape key armature = None if modifiers is not None: modifiers_dict = {m.type: m for m in modifiers} if "ARMATURE" in modifiers_dict: modifier = modifiers_dict["ARMATURE"] armature = modifier.object # # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. # for blender_polygon in blender_mesh.polygons: export_color = True # if export_settings['gltf_materials'] is False: primitive = material_idx_to_primitives[0] vertex_index_to_new_indices = material_map[0] elif not blender_polygon.material_index in material_idx_to_primitives: primitive = material_idx_to_primitives[0] vertex_index_to_new_indices = material_map[0] else: primitive = material_idx_to_primitives[ blender_polygon.material_index] vertex_index_to_new_indices = material_map[ blender_polygon.material_index] # attributes = primitive[ATTRIBUTES_ID] face_normal = blender_polygon.normal face_tangent = Vector((0.0, 0.0, 0.0)) face_bitangent = Vector((0.0, 0.0, 0.0)) if use_tangents: for loop_index in blender_polygon.loop_indices: temp_vertex = blender_mesh.loops[loop_index] face_tangent += temp_vertex.tangent face_bitangent += temp_vertex.bitangent face_tangent.normalize() face_bitangent.normalize() # indices = primitive[INDICES_ID] loop_index_list = [] if len(blender_polygon.loop_indices) == 3: loop_index_list.extend(blender_polygon.loop_indices) elif len(blender_polygon.loop_indices) > 3: # Triangulation of polygon. Using internal function, as non-convex polygons could exist. polyline = [] for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[loop_index].vertex_index v = blender_mesh.vertices[vertex_index].co polyline.append(Vector((v[0], v[1], v[2]))) triangles = tessellate_polygon((polyline, )) for triangle in triangles: for triangle_index in triangle: loop_index_list.append( blender_polygon.loop_indices[triangle_index]) else: continue for loop_index in loop_index_list: vertex_index = blender_mesh.loops[loop_index].vertex_index if vertex_index_to_new_indices.get(vertex_index) is None: vertex_index_to_new_indices[vertex_index] = [] # v = None n = None t = None b = None uvs = [] colors = [] joints = [] weights = [] target_positions = [] target_normals = [] target_tangents = [] vertex = blender_mesh.vertices[vertex_index] v = convert_swizzle_location(vertex.co, armature, blender_object, export_settings) if blender_polygon.use_smooth or blender_mesh.use_auto_smooth: if blender_mesh.has_custom_normals: n = convert_swizzle_normal( blender_mesh.loops[loop_index].normal, armature, blender_object, export_settings) else: n = convert_swizzle_normal(vertex.normal, armature, blender_object, export_settings) if use_tangents: t = convert_swizzle_tangent( blender_mesh.loops[loop_index].tangent, armature, blender_object, export_settings) b = convert_swizzle_location( blender_mesh.loops[loop_index].bitangent, armature, blender_object, export_settings) else: n = convert_swizzle_normal(face_normal, armature, blender_object, export_settings) if use_tangents: t = convert_swizzle_tangent(face_tangent, armature, blender_object, export_settings) b = convert_swizzle_location(face_bitangent, armature, blender_object, export_settings) if use_tangents: tv = Vector((t[0], t[1], t[2])) bv = Vector((b[0], b[1], b[2])) nv = Vector((n[0], n[1], n[2])) if (nv.cross(tv)).dot(bv) < 0.0: t[3] = -1.0 if blender_mesh.uv_layers.active: for tex_coord_index in range(0, tex_coord_max): uv = blender_mesh.uv_layers[tex_coord_index].data[ loop_index].uv uvs.append([uv.x, 1.0 - uv.y]) # if color_max > 0 and export_color: for color_index in range(0, color_max): color_name = COLOR_PREFIX + str(color_index) color = vertex_colors[color_name].data[loop_index].color colors.append([ color_srgb_to_scene_linear(color[0]), color_srgb_to_scene_linear(color[1]), color_srgb_to_scene_linear(color[2]), color[3] ]) # bone_count = 0 if blender_vertex_groups is not None and vertex.groups is not None and len( vertex.groups) > 0 and export_settings[ gltf2_blender_export_keys.SKINS]: joint = [] weight = [] vertex_groups = vertex.groups if not export_settings['gltf_all_vertex_influences']: # sort groups by weight descending vertex_groups = sorted(vertex.groups, key=attrgetter('weight'), reverse=True) for group_element in vertex_groups: if len(joint) == 4: bone_count += 1 joints.append(joint) weights.append(weight) joint = [] weight = [] # joint_weight = group_element.weight if joint_weight <= 0.0: continue # vertex_group_index = group_element.group if vertex_group_index < 0 or vertex_group_index >= len( blender_vertex_groups): continue vertex_group_name = blender_vertex_groups[ vertex_group_index].name joint_index = None if armature: skin = gltf2_blender_gather_skins.gather_skin( armature, export_settings) for index, j in enumerate(skin.joints): if j.name == vertex_group_name: joint_index = index break # if joint_index is not None: joint.append(joint_index) weight.append(joint_weight) if len(joint) > 0: bone_count += 1 for fill in range(0, 4 - len(joint)): joint.append(0) weight.append(0.0) joints.append(joint) weights.append(weight) for fill in range(0, bone_max - bone_count): joints.append([0, 0, 0, 0]) weights.append([0.0, 0.0, 0.0, 0.0]) # if morph_max > 0 and export_settings[ gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): blender_shape_key = blender_shape_keys[morph_index] v_morph = convert_swizzle_location( blender_shape_key.shape_key.data[vertex_index].co, armature, blender_object, export_settings) # Store delta. v_morph -= v target_positions.append(v_morph) # n_morph = None if blender_polygon.use_smooth: temp_normals = blender_shape_key.vertex_normals n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1], temp_normals[vertex_index * 3 + 2]) else: temp_normals = blender_shape_key.polygon_normals n_morph = (temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], temp_normals[blender_polygon.index * 3 + 2]) n_morph = convert_swizzle_normal(Vector(n_morph), armature, blender_object, export_settings) # Store delta. n_morph -= n target_normals.append(n_morph) # if use_tangents: rotation = n_morph.rotation_difference(n) t_morph = Vector((t[0], t[1], t[2])) t_morph.rotate(rotation) target_tangents.append(t_morph) # # create = True for current_new_index in vertex_index_to_new_indices[vertex_index]: found = True for i in range(0, 3): if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]: found = False break if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]: found = False break if use_tangents: for i in range(0, 4): if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]: found = False break if not found: continue for tex_coord_index in range(0, tex_coord_max): uv = uvs[tex_coord_index] tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) for i in range(0, 2): if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]: found = False break if export_color: for color_index in range(0, color_max): color = colors[color_index] color_id = COLOR_PREFIX + str(color_index) for i in range(0, 3): # Alpha is always 1.0 - see above. current_color = attributes[color_id][ current_new_index * 4 + i] if color_srgb_to_scene_linear( current_color) != color[i]: found = False break if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joint = joints[bone_index] weight = weights[bone_index] joint_id = JOINTS_PREFIX + str(bone_index) weight_id = WEIGHTS_PREFIX + str(bone_index) for i in range(0, 4): if attributes[joint_id][current_new_index * 4 + i] != joint[i]: found = False break if attributes[weight_id][current_new_index * 4 + i] != weight[i]: found = False break if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_position = target_positions[morph_index] target_normal = target_normals[morph_index] if use_tangents: target_tangent = target_tangents[morph_index] target_position_id = MORPH_POSITION_PREFIX + str( morph_index) target_normal_id = MORPH_NORMAL_PREFIX + str( morph_index) target_tangent_id = MORPH_TANGENT_PREFIX + str( morph_index) for i in range(0, 3): if attributes[target_position_id][ current_new_index * 3 + i] != target_position[i]: found = False break if attributes[target_normal_id][ current_new_index * 3 + i] != target_normal[i]: found = False break if use_tangents: if attributes[target_tangent_id][ current_new_index * 3 + i] != target_tangent[i]: found = False break if found: indices.append(current_new_index) create = False break if not create: continue new_index = 0 if primitive.get('max_index') is not None: new_index = primitive['max_index'] + 1 primitive['max_index'] = new_index vertex_index_to_new_indices[vertex_index].append(new_index) # # indices.append(new_index) # attributes[POSITION_ATTRIBUTE].extend(v) attributes[NORMAL_ATTRIBUTE].extend(n) if use_tangents: attributes[TANGENT_ATTRIBUTE].extend(t) if blender_mesh.uv_layers.active: for tex_coord_index in range(0, tex_coord_max): tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) if attributes.get(tex_coord_id) is None: attributes[tex_coord_id] = [] attributes[tex_coord_id].extend(uvs[tex_coord_index]) if export_color: for color_index in range(0, color_max): color_id = COLOR_PREFIX + str(color_index) if attributes.get(color_id) is None: attributes[color_id] = [] attributes[color_id].extend(colors[color_index]) if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joint_id = JOINTS_PREFIX + str(bone_index) if attributes.get(joint_id) is None: attributes[joint_id] = [] attributes[joint_id].extend(joints[bone_index]) weight_id = WEIGHTS_PREFIX + str(bone_index) if attributes.get(weight_id) is None: attributes[weight_id] = [] attributes[weight_id].extend(weights[bone_index]) if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_position_id = MORPH_POSITION_PREFIX + str( morph_index) if attributes.get(target_position_id) is None: attributes[target_position_id] = [] attributes[target_position_id].extend( target_positions[morph_index]) target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) if attributes.get(target_normal_id) is None: attributes[target_normal_id] = [] attributes[target_normal_id].extend( target_normals[morph_index]) if use_tangents: target_tangent_id = MORPH_TANGENT_PREFIX + str( morph_index) if attributes.get(target_tangent_id) is None: attributes[target_tangent_id] = [] attributes[target_tangent_id].extend( target_tangents[morph_index]) # # Add non-empty primitives # result_primitives = [ primitive for primitive in material_idx_to_primitives.values() if len(primitive[INDICES_ID]) != 0 ] print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) return result_primitives
def __gather_skin(blender_object, export_settings): if "Armature" in blender_object.modifiers: # Skins and meshes must be in the same glTF node, which is different from how blender handles armatures return gltf2_blender_gather_skins.gather_skin( blender_object.modifiers["Armature"].object, export_settings)
def extract_primitives(glTF, blender_mesh, blender_vertex_groups, modifiers, export_settings): """ Extract primitives from a mesh. Polygons are triangulated and sorted by material. Furthermore, primitives are split up, if the indices range is exceeded. Finally, triangles are also split up/duplicated, if face normals are used instead of vertex normals. """ print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) use_tangents = False if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: try: blender_mesh.calc_tangents() use_tangents = True except Exception: print_console('WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.') # material_map = {} # # Gathering position, normal and tex_coords. # no_material_attributes = { POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: [] } if use_tangents: no_material_attributes[TANGENT_ATTRIBUTE] = [] # # Directory of materials with its primitive. # no_material_primitives = { MATERIAL_ID: '', INDICES_ID: [], ATTRIBUTES_ID: no_material_attributes } material_name_to_primitives = {'': no_material_primitives} # vertex_index_to_new_indices = {} material_map[''] = vertex_index_to_new_indices # # Create primitive for each material. # for blender_material in blender_mesh.materials: if blender_material is None: continue attributes = { POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: [] } if use_tangents: attributes[TANGENT_ATTRIBUTE] = [] primitive = { MATERIAL_ID: blender_material.name, INDICES_ID: [], ATTRIBUTES_ID: attributes } material_name_to_primitives[blender_material.name] = primitive # vertex_index_to_new_indices = {} material_map[blender_material.name] = vertex_index_to_new_indices tex_coord_max = 0 if blender_mesh.uv_layers.active: tex_coord_max = len(blender_mesh.uv_layers) # vertex_colors = {} color_index = 0 for vertex_color in blender_mesh.vertex_colors: vertex_color_name = COLOR_PREFIX + str(color_index) vertex_colors[vertex_color_name] = vertex_color color_index += 1 if color_index >= GLTF_MAX_COLORS: break color_max = color_index # bone_max = 0 for blender_polygon in blender_mesh.polygons: for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[loop_index].vertex_index bones_count = len(blender_mesh.vertices[vertex_index].groups) if bones_count > 0: if bones_count % 4 == 0: bones_count -= 1 bone_max = max(bone_max, bones_count // 4 + 1) # morph_max = 0 blender_shape_keys = [] if blender_mesh.shape_keys is not None: morph_max = len(blender_mesh.shape_keys.key_blocks) - 1 for blender_shape_key in blender_mesh.shape_keys.key_blocks: if blender_shape_key != blender_shape_key.relative_key: blender_shape_keys.append(ShapeKey( blender_shape_key, blender_shape_key.normals_vertex_get(), # calculate vertex normals for this shape key blender_shape_key.normals_polygon_get())) # calculate polygon normals for this shape key # # Convert polygon to primitive indices and eliminate invalid ones. Assign to material. # for blender_polygon in blender_mesh.polygons: export_color = True # if blender_polygon.material_index < 0 or blender_polygon.material_index >= len(blender_mesh.materials) or \ blender_mesh.materials[blender_polygon.material_index] is None: primitive = material_name_to_primitives[''] vertex_index_to_new_indices = material_map[''] else: primitive = material_name_to_primitives[blender_mesh.materials[blender_polygon.material_index].name] vertex_index_to_new_indices = material_map[blender_mesh.materials[blender_polygon.material_index].name] # attributes = primitive[ATTRIBUTES_ID] face_normal = blender_polygon.normal face_tangent = Vector((0.0, 0.0, 0.0)) face_bitangent = Vector((0.0, 0.0, 0.0)) if use_tangents: for loop_index in blender_polygon.loop_indices: temp_vertex = blender_mesh.loops[loop_index] face_tangent += temp_vertex.tangent face_bitangent += temp_vertex.bitangent face_tangent.normalize() face_bitangent.normalize() # indices = primitive[INDICES_ID] loop_index_list = [] if len(blender_polygon.loop_indices) == 3: loop_index_list.extend(blender_polygon.loop_indices) elif len(blender_polygon.loop_indices) > 3: # Triangulation of polygon. Using internal function, as non-convex polygons could exist. polyline = [] for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[loop_index].vertex_index v = blender_mesh.vertices[vertex_index].co polyline.append(Vector((v[0], v[1], v[2]))) triangles = tessellate_polygon((polyline,)) for triangle in triangles: loop_index_list.append(blender_polygon.loop_indices[triangle[0]]) loop_index_list.append(blender_polygon.loop_indices[triangle[2]]) loop_index_list.append(blender_polygon.loop_indices[triangle[1]]) else: continue for loop_index in loop_index_list: vertex_index = blender_mesh.loops[loop_index].vertex_index if vertex_index_to_new_indices.get(vertex_index) is None: vertex_index_to_new_indices[vertex_index] = [] # v = None n = None t = None b = None uvs = [] colors = [] joints = [] weights = [] target_positions = [] target_normals = [] target_tangents = [] vertex = blender_mesh.vertices[vertex_index] v = convert_swizzle_location(vertex.co, export_settings) if blender_polygon.use_smooth: if blender_mesh.has_custom_normals: n = convert_swizzle_location(blender_mesh.loops[loop_index].normal, export_settings) else: n = convert_swizzle_location(vertex.normal, export_settings) if use_tangents: t = convert_swizzle_tangent(blender_mesh.loops[loop_index].tangent, export_settings) b = convert_swizzle_location(blender_mesh.loops[loop_index].bitangent, export_settings) else: n = convert_swizzle_location(face_normal, export_settings) if use_tangents: t = convert_swizzle_tangent(face_tangent, export_settings) b = convert_swizzle_location(face_bitangent, export_settings) if use_tangents: tv = Vector((t[0], t[1], t[2])) bv = Vector((b[0], b[1], b[2])) nv = Vector((n[0], n[1], n[2])) if (nv.cross(tv)).dot(bv) < 0.0: t[3] = -1.0 if blender_mesh.uv_layers.active: for tex_coord_index in range(0, tex_coord_max): uv = blender_mesh.uv_layers[tex_coord_index].data[loop_index].uv uvs.append([uv.x, 1.0 - uv.y]) # if color_max > 0 and export_color: for color_index in range(0, color_max): color_name = COLOR_PREFIX + str(color_index) color = vertex_colors[color_name].data[loop_index].color colors.append([ color_srgb_to_scene_linear(color[0]), color_srgb_to_scene_linear(color[1]), color_srgb_to_scene_linear(color[2]), 1.0 ]) # bone_count = 0 if blender_vertex_groups is not None and vertex.groups is not None and len(vertex.groups) > 0 and export_settings[gltf2_blender_export_keys.SKINS]: joint = [] weight = [] vertex_groups = vertex.groups if not export_settings['gltf_all_vertex_influences']: # sort groups by weight descending vertex_groups = sorted(vertex.groups, key=attrgetter('weight'), reverse=True) for group_element in vertex_groups: if len(joint) == 4: bone_count += 1 joints.append(joint) weights.append(weight) joint = [] weight = [] # joint_weight = group_element.weight if joint_weight <= 0.0: continue # vertex_group_index = group_element.group vertex_group_name = blender_vertex_groups[vertex_group_index].name joint_index = None if modifiers is not None: modifiers_dict = {m.type: m for m in modifiers} if "ARMATURE" in modifiers_dict: modifier = modifiers_dict["ARMATURE"] armature = modifier.object if armature: skin = gltf2_blender_gather_skins.gather_skin(armature, modifier.id_data, export_settings) for index, j in enumerate(skin.joints): if j.name == vertex_group_name: joint_index = index break # if joint_index is not None: joint.append(joint_index) weight.append(joint_weight) if len(joint) > 0: bone_count += 1 for fill in range(0, 4 - len(joint)): joint.append(0) weight.append(0.0) joints.append(joint) weights.append(weight) for fill in range(0, bone_max - bone_count): joints.append([0, 0, 0, 0]) weights.append([0.0, 0.0, 0.0, 0.0]) # if morph_max > 0 and export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): blender_shape_key = blender_shape_keys[morph_index] v_morph = convert_swizzle_location(blender_shape_key.shape_key.data[vertex_index].co, export_settings) # Store delta. v_morph -= v target_positions.append(v_morph) # n_morph = None if blender_polygon.use_smooth: temp_normals = blender_shape_key.vertex_normals n_morph = (temp_normals[vertex_index * 3 + 0], temp_normals[vertex_index * 3 + 1], temp_normals[vertex_index * 3 + 2]) else: temp_normals = blender_shape_key.polygon_normals n_morph = ( temp_normals[blender_polygon.index * 3 + 0], temp_normals[blender_polygon.index * 3 + 1], temp_normals[blender_polygon.index * 3 + 2]) n_morph = convert_swizzle_location(n_morph, export_settings) # Store delta. n_morph -= n target_normals.append(n_morph) # if use_tangents: rotation = n_morph.rotation_difference(n) t_morph = Vector((t[0], t[1], t[2])) t_morph.rotate(rotation) target_tangents.append(t_morph) # # create = True for current_new_index in vertex_index_to_new_indices[vertex_index]: found = True for i in range(0, 3): if attributes[POSITION_ATTRIBUTE][current_new_index * 3 + i] != v[i]: found = False break if attributes[NORMAL_ATTRIBUTE][current_new_index * 3 + i] != n[i]: found = False break if use_tangents: for i in range(0, 4): if attributes[TANGENT_ATTRIBUTE][current_new_index * 4 + i] != t[i]: found = False break if not found: continue for tex_coord_index in range(0, tex_coord_max): uv = uvs[tex_coord_index] tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) for i in range(0, 2): if attributes[tex_coord_id][current_new_index * 2 + i] != uv[i]: found = False break if export_color: for color_index in range(0, color_max): color = colors[color_index] color_id = COLOR_PREFIX + str(color_index) for i in range(0, 3): # Alpha is always 1.0 - see above. current_color = attributes[color_id][current_new_index * 4 + i] if color_srgb_to_scene_linear(current_color) != color[i]: found = False break if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joint = joints[bone_index] weight = weights[bone_index] joint_id = JOINTS_PREFIX + str(bone_index) weight_id = WEIGHTS_PREFIX + str(bone_index) for i in range(0, 4): if attributes[joint_id][current_new_index * 4 + i] != joint[i]: found = False break if attributes[weight_id][current_new_index * 4 + i] != weight[i]: found = False break if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_position = target_positions[morph_index] target_normal = target_normals[morph_index] if use_tangents: target_tangent = target_tangents[morph_index] target_position_id = MORPH_POSITION_PREFIX + str(morph_index) target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) for i in range(0, 3): if attributes[target_position_id][current_new_index * 3 + i] != target_position[i]: found = False break if attributes[target_normal_id][current_new_index * 3 + i] != target_normal[i]: found = False break if use_tangents: if attributes[target_tangent_id][current_new_index * 3 + i] != target_tangent[i]: found = False break if found: indices.append(current_new_index) create = False break if not create: continue new_index = 0 if primitive.get('max_index') is not None: new_index = primitive['max_index'] + 1 primitive['max_index'] = new_index vertex_index_to_new_indices[vertex_index].append(new_index) # # indices.append(new_index) # attributes[POSITION_ATTRIBUTE].extend(v) attributes[NORMAL_ATTRIBUTE].extend(n) if use_tangents: attributes[TANGENT_ATTRIBUTE].extend(t) if blender_mesh.uv_layers.active: for tex_coord_index in range(0, tex_coord_max): tex_coord_id = TEXCOORD_PREFIX + str(tex_coord_index) if attributes.get(tex_coord_id) is None: attributes[tex_coord_id] = [] attributes[tex_coord_id].extend(uvs[tex_coord_index]) if export_color: for color_index in range(0, color_max): color_id = COLOR_PREFIX + str(color_index) if attributes.get(color_id) is None: attributes[color_id] = [] attributes[color_id].extend(colors[color_index]) if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joint_id = JOINTS_PREFIX + str(bone_index) if attributes.get(joint_id) is None: attributes[joint_id] = [] attributes[joint_id].extend(joints[bone_index]) weight_id = WEIGHTS_PREFIX + str(bone_index) if attributes.get(weight_id) is None: attributes[weight_id] = [] attributes[weight_id].extend(weights[bone_index]) if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_position_id = MORPH_POSITION_PREFIX + str(morph_index) if attributes.get(target_position_id) is None: attributes[target_position_id] = [] attributes[target_position_id].extend(target_positions[morph_index]) target_normal_id = MORPH_NORMAL_PREFIX + str(morph_index) if attributes.get(target_normal_id) is None: attributes[target_normal_id] = [] attributes[target_normal_id].extend(target_normals[morph_index]) if use_tangents: target_tangent_id = MORPH_TANGENT_PREFIX + str(morph_index) if attributes.get(target_tangent_id) is None: attributes[target_tangent_id] = [] attributes[target_tangent_id].extend(target_tangents[morph_index]) # # Add primitive plus split them if needed. # result_primitives = [] for material_name, primitive in material_name_to_primitives.items(): export_color = True # indices = primitive[INDICES_ID] if len(indices) == 0: continue position = primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE] normal = primitive[ATTRIBUTES_ID][NORMAL_ATTRIBUTE] if use_tangents: tangent = primitive[ATTRIBUTES_ID][TANGENT_ATTRIBUTE] tex_coords = [] for tex_coord_index in range(0, tex_coord_max): tex_coords.append(primitive[ATTRIBUTES_ID][TEXCOORD_PREFIX + str(tex_coord_index)]) colors = [] if export_color: for color_index in range(0, color_max): tex_coords.append(primitive[ATTRIBUTES_ID][COLOR_PREFIX + str(color_index)]) joints = [] weights = [] if export_settings[gltf2_blender_export_keys.SKINS]: for bone_index in range(0, bone_max): joints.append(primitive[ATTRIBUTES_ID][JOINTS_PREFIX + str(bone_index)]) weights.append(primitive[ATTRIBUTES_ID][WEIGHTS_PREFIX + str(bone_index)]) target_positions = [] target_normals = [] target_tangents = [] if export_settings[gltf2_blender_export_keys.MORPH]: for morph_index in range(0, morph_max): target_positions.append(primitive[ATTRIBUTES_ID][MORPH_POSITION_PREFIX + str(morph_index)]) target_normals.append(primitive[ATTRIBUTES_ID][MORPH_NORMAL_PREFIX + str(morph_index)]) if use_tangents: target_tangents.append(primitive[ATTRIBUTES_ID][MORPH_TANGENT_PREFIX + str(morph_index)]) # count = len(indices) if count == 0: continue max_index = max(indices) # # NOTE: Values used by some graphics APIs as "primitive restart" values are disallowed. # Specifically, the value 65535 (in UINT16) cannot be used as a vertex index. # https://github.com/KhronosGroup/glTF/issues/1142 # https://github.com/KhronosGroup/glTF/pull/1476/files range_indices = 65535 # if max_index >= range_indices: # # Splitting result_primitives. # # At start, all indices are pending. pending_attributes = { POSITION_ATTRIBUTE: [], NORMAL_ATTRIBUTE: [] } if use_tangents: pending_attributes[TANGENT_ATTRIBUTE] = [] pending_primitive = { MATERIAL_ID: material_name, INDICES_ID: [], ATTRIBUTES_ID: pending_attributes } pending_primitive[INDICES_ID].extend(indices) pending_attributes[POSITION_ATTRIBUTE].extend(position) pending_attributes[NORMAL_ATTRIBUTE].extend(normal) if use_tangents: pending_attributes[TANGENT_ATTRIBUTE].extend(tangent) tex_coord_index = 0 for tex_coord in tex_coords: pending_attributes[TEXCOORD_PREFIX + str(tex_coord_index)] = tex_coord tex_coord_index += 1 if export_color: color_index = 0 for color in colors: pending_attributes[COLOR_PREFIX + str(color_index)] = color color_index += 1 if export_settings[gltf2_blender_export_keys.SKINS]: joint_index = 0 for joint in joints: pending_attributes[JOINTS_PREFIX + str(joint_index)] = joint joint_index += 1 weight_index = 0 for weight in weights: pending_attributes[WEIGHTS_PREFIX + str(weight_index)] = weight weight_index += 1 if export_settings[gltf2_blender_export_keys.MORPH]: morph_index = 0 for target_position in target_positions: pending_attributes[MORPH_POSITION_PREFIX + str(morph_index)] = target_position morph_index += 1 morph_index = 0 for target_normal in target_normals: pending_attributes[MORPH_NORMAL_PREFIX + str(morph_index)] = target_normal morph_index += 1 if use_tangents: morph_index = 0 for target_tangent in target_tangents: pending_attributes[MORPH_TANGENT_PREFIX + str(morph_index)] = target_tangent morph_index += 1 pending_indices = pending_primitive[INDICES_ID] # Continue until all are processed. while len(pending_indices) > 0: process_indices = pending_primitive[INDICES_ID] max_index = max(process_indices) pending_indices = [] # # all_local_indices = [] for i in range(0, (max_index // range_indices) + 1): all_local_indices.append([]) # # # For all faces ... for face_index in range(0, len(process_indices), 3): written = False face_min_index = min(process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2]) face_max_index = max(process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2]) # ... check if it can be but in a range of maximum indices. for i in range(0, (max_index // range_indices) + 1): offset = i * range_indices # Yes, so store the primitive with its indices. if face_min_index >= offset and face_max_index < offset + range_indices: all_local_indices[i].extend( [process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2]]) written = True break # If not written, the triangle face has indices from different ranges. if not written: pending_indices.extend([process_indices[face_index + 0], process_indices[face_index + 1], process_indices[face_index + 2]]) # Only add result_primitives, which do have indices in it. for local_indices in all_local_indices: if len(local_indices) > 0: current_primitive = extract_primitive_floor(pending_primitive, local_indices, use_tangents) result_primitives.append(current_primitive) print_console('DEBUG', 'Adding primitive with splitting. Indices: ' + str( len(current_primitive[INDICES_ID])) + ' Vertices: ' + str( len(current_primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3)) # Process primitive faces having indices in several ranges. if len(pending_indices) > 0: pending_primitive = extract_primitive_pack(pending_primitive, pending_indices, use_tangents) print_console('DEBUG', 'Creating temporary primitive for splitting') else: # # No splitting needed. # result_primitives.append(primitive) print_console('DEBUG', 'Adding primitive without splitting. Indices: ' + str( len(primitive[INDICES_ID])) + ' Vertices: ' + str( len(primitive[ATTRIBUTES_ID][POSITION_ATTRIBUTE]) // 3)) print_console('INFO', 'Primitives created: ' + str(len(result_primitives))) return result_primitives
def extract_primitives(glTF, blender_mesh, library, blender_object, blender_vertex_groups, modifiers, export_settings): """ Extract primitives from a mesh. Polygons are triangulated and sorted by material. Vertices in multiple faces get split up as necessary. """ print_console('INFO', 'Extracting primitive: ' + blender_mesh.name) # # First, decide what attributes to gather (eg. how many COLOR_n, etc.) # Also calculate normals/tangents now if necessary. # use_normals = export_settings[gltf2_blender_export_keys.NORMALS] if use_normals: blender_mesh.calc_normals_split() use_tangents = False if use_normals and export_settings[gltf2_blender_export_keys.TANGENTS]: if blender_mesh.uv_layers.active and len(blender_mesh.uv_layers) > 0: try: blender_mesh.calc_tangents() use_tangents = True except Exception: print_console( 'WARNING', 'Could not calculate tangents. Please try to triangulate the mesh first.' ) tex_coord_max = 0 if export_settings[gltf2_blender_export_keys.TEX_COORDS]: if blender_mesh.uv_layers.active: tex_coord_max = len(blender_mesh.uv_layers) color_max = 0 if export_settings[gltf2_blender_export_keys.COLORS]: color_max = len(blender_mesh.vertex_colors) bone_max = 0 # number of JOINTS_n sets needed (1 set = 4 influences) armature = None if blender_vertex_groups and export_settings[ gltf2_blender_export_keys.SKINS]: if modifiers is not None: modifiers_dict = {m.type: m for m in modifiers} if "ARMATURE" in modifiers_dict: modifier = modifiers_dict["ARMATURE"] armature = modifier.object # Skin must be ignored if the object is parented to a bone of the armature # (This creates an infinite recursive error) # So ignoring skin in that case is_child_of_arma = (armature and blender_object and blender_object.parent_type == "BONE" and blender_object.parent.name == armature.name) if is_child_of_arma: armature = None if armature: skin = gltf2_blender_gather_skins.gather_skin( armature, export_settings) if not skin: armature = None else: joint_name_to_index = { joint.name: index for index, joint in enumerate(skin.joints) } group_to_joint = [ joint_name_to_index.get(g.name) for g in blender_vertex_groups ] # Find out max number of bone influences for blender_polygon in blender_mesh.polygons: for loop_index in blender_polygon.loop_indices: vertex_index = blender_mesh.loops[ loop_index].vertex_index groups_count = len( blender_mesh.vertices[vertex_index].groups) bones_count = (groups_count + 3) // 4 bone_max = max(bone_max, bones_count) use_morph_normals = use_normals and export_settings[ gltf2_blender_export_keys.MORPH_NORMAL] use_morph_tangents = use_morph_normals and use_tangents and export_settings[ gltf2_blender_export_keys.MORPH_TANGENT] shape_keys = [] if blender_mesh.shape_keys and export_settings[ gltf2_blender_export_keys.MORPH]: for blender_shape_key in blender_mesh.shape_keys.key_blocks: if blender_shape_key == blender_shape_key.relative_key or blender_shape_key.mute: continue split_normals = None if use_morph_normals: split_normals = blender_shape_key.normals_split_get() shape_keys.append(ShapeKey( blender_shape_key, split_normals, )) use_materials = export_settings[gltf2_blender_export_keys.MATERIALS] # # Gather the verts and indices for each primitive. # prims = {} blender_mesh.calc_loop_triangles() for loop_tri in blender_mesh.loop_triangles: blender_polygon = blender_mesh.polygons[loop_tri.polygon_index] material_idx = -1 if use_materials: material_idx = blender_polygon.material_index prim = prims.get(material_idx) if not prim: prim = Prim() prims[material_idx] = prim for loop_index in loop_tri.loops: vertex_index = blender_mesh.loops[loop_index].vertex_index vertex = blender_mesh.vertices[vertex_index] # vert will be a tuple of all the vertex attributes. # Used as cache key in prim.verts. vert = (vertex_index, ) v = vertex.co vert += ((v[0], v[1], v[2]), ) if use_normals: n = blender_mesh.loops[loop_index].normal vert += ((n[0], n[1], n[2]), ) if use_tangents: t = blender_mesh.loops[loop_index].tangent b = blender_mesh.loops[loop_index].bitangent vert += ((t[0], t[1], t[2]), ) vert += ((b[0], b[1], b[2]), ) # TODO: store just bitangent_sign in vert, not whole bitangent? for tex_coord_index in range(0, tex_coord_max): uv = blender_mesh.uv_layers[tex_coord_index].data[ loop_index].uv uv = (uv.x, 1.0 - uv.y) vert += (uv, ) for color_index in range(0, color_max): color = blender_mesh.vertex_colors[color_index].data[ loop_index].color col = ( color_srgb_to_scene_linear(color[0]), color_srgb_to_scene_linear(color[1]), color_srgb_to_scene_linear(color[2]), color[3], ) vert += (col, ) if bone_max: bones = [] if vertex.groups: for group_element in vertex.groups: weight = group_element.weight if weight <= 0.0: continue try: joint = group_to_joint[group_element.group] except Exception: continue if joint is None: continue bones.append((joint, weight)) bones.sort(key=lambda x: x[1], reverse=True) bones = tuple(bones) vert += (bones, ) for shape_key in shape_keys: v_morph = shape_key.shape_key.data[vertex_index].co v_morph = v_morph - v # store delta vert += ((v_morph[0], v_morph[1], v_morph[2]), ) if use_morph_normals: normals = shape_key.split_normals n_morph = Vector(normals[loop_index * 3:loop_index * 3 + 3]) n_morph = n_morph - n # store delta vert += ((n_morph[0], n_morph[1], n_morph[2]), ) vert_idx = prim.verts.setdefault(vert, len(prim.verts)) prim.indices.append(vert_idx) # # Put the verts into attribute arrays. # result_primitives = [] for material_idx, prim in prims.items(): if not prim.indices: continue vs = [] ns = [] ts = [] uvs = [[] for _ in range(tex_coord_max)] cols = [[] for _ in range(color_max)] joints = [[] for _ in range(bone_max)] weights = [[] for _ in range(bone_max)] vs_morph = [[] for _ in shape_keys] ns_morph = [[] for _ in shape_keys] ts_morph = [[] for _ in shape_keys] for vert in prim.verts.keys(): i = 0 i += 1 # skip over Blender mesh index v = vert[i] i += 1 v = convert_swizzle_location(v, armature, blender_object, export_settings) vs.extend(v) if use_normals: n = vert[i] i += 1 n = convert_swizzle_normal(n, armature, blender_object, export_settings) ns.extend(n) if use_tangents: t = vert[i] i += 1 t = convert_swizzle_tangent(t, armature, blender_object, export_settings) ts.extend(t) b = vert[i] i += 1 b = convert_swizzle_tangent(b, armature, blender_object, export_settings) b_sign = -1.0 if (Vector(n).cross(Vector(t))).dot( Vector(b)) < 0.0 else 1.0 ts.append(b_sign) for tex_coord_index in range(0, tex_coord_max): uv = vert[i] i += 1 uvs[tex_coord_index].extend(uv) for color_index in range(0, color_max): col = vert[i] i += 1 cols[color_index].extend(col) if bone_max: bones = vert[i] i += 1 for j in range(0, 4 * bone_max): if j < len(bones): joint, weight = bones[j] else: joint, weight = 0, 0.0 joints[j // 4].append(joint) weights[j // 4].append(weight) for shape_key_index in range(0, len(shape_keys)): v_morph = vert[i] i += 1 v_morph = convert_swizzle_location(v_morph, armature, blender_object, export_settings) vs_morph[shape_key_index].extend(v_morph) if use_morph_normals: n_morph = vert[i] i += 1 n_morph = convert_swizzle_normal(n_morph, armature, blender_object, export_settings) ns_morph[shape_key_index].extend(n_morph) if use_morph_tangents: rotation = n_morph.rotation_difference(n) t_morph = Vector(t) t_morph.rotate(rotation) ts_morph[shape_key_index].extend(t_morph) attributes = {} attributes['POSITION'] = vs if ns: attributes['NORMAL'] = ns if ts: attributes['TANGENT'] = ts for i, uv in enumerate(uvs): attributes['TEXCOORD_%d' % i] = uv for i, col in enumerate(cols): attributes['COLOR_%d' % i] = col for i, js in enumerate(joints): attributes['JOINTS_%d' % i] = js for i, ws in enumerate(weights): attributes['WEIGHTS_%d' % i] = ws for i, vm in enumerate(vs_morph): attributes['MORPH_POSITION_%d' % i] = vm for i, nm in enumerate(ns_morph): attributes['MORPH_NORMAL_%d' % i] = nm for i, tm in enumerate(ts_morph): attributes['MORPH_TANGENT_%d' % i] = tm result_primitives.append({ 'attributes': attributes, 'indices': prim.indices, 'material': material_idx, }) print_console('INFO', 'Primitives created: %d' % len(result_primitives)) return result_primitives