public CSkeletalMesh(USkeletalMesh mesh) { OriginalMesh = mesh; // convert bounds BoundingSphere = new FSphere { R = mesh.Bounds.sphere_radius / 2 }; BoundingBox = new FBox { Min = mesh.Bounds.origin - mesh.Bounds.box_extend, Max = mesh.Bounds.origin + mesh.Bounds.box_extend }; // MeshScale, MeshOrigin, RotOrigin are removed in UE4 //!! NOTE: MeshScale is integrated into RefSkeleton.RefBonePose[0].Scale3D. //!! Perhaps rotation/translation are integrated too! MeshOrigin = new CVec3 { v = new float[] { 0, 0, 0 } }; RotOrigin = new FRotator { pitch = 0, roll = 0, yaw = 0 }; MeshScale = new CVec3 { v = new float[] { 1, 1, 1 } }; // convert LODs Lods = new CSkelMeshLod[mesh.LODModels.Length]; for (int i = 0; i < Lods.Length; i++) { var SrcLod = mesh.LODModels[i]; if (SrcLod.Indices.Indices16.Length == 0 && SrcLod.Indices.Indices32.Length == 0) { // No indicies in this lod continue; } int NumTexCoords = SrcLod.NumTexCoords; if (NumTexCoords > 8) { throw new FileLoadException($"SkeletalMesh has too many ({NumTexCoords}) UV sets"); } CSkelMeshLod Lod = new CSkelMeshLod { NumTexCoords = NumTexCoords, HasNormals = true, HasTangents = true, }; // get vertex count and determine vertex source int VertexCount = SrcLod.VertexBufferGPUSkin.GetVertexCount(); bool bUseVerticesFromSections = false; // if (VertexCount == 0 && SrcLod.Sections.Length > 0 && SrcLod.Sections[0].SoftVertices.Count > 0) // above is used for editor assets, but there are no chunks for soft vertices Lod.AllocateVerts(VertexCount); int chunkIndex = -1; int lastChunkVertex = -1; int chunkVertexIndex = 0; ushort[] BoneMap = null; for (int j = 0; j < VertexCount; j++) { while (j >= lastChunkVertex) // this will fix any issues with empty chunks or sections { // UE4.13+ code: chunk information migrated to sections FSkelMeshSection S = SrcLod.Sections[++chunkIndex]; lastChunkVertex = (int)(S.base_vertex_index + S.num_vertices); BoneMap = S.bone_map; chunkVertexIndex = 0; } // get vertex from GPU skin FSkelMeshVertexBase V; if (!SrcLod.VertexBufferGPUSkin.bUseFullPrecisionUVs) { FGPUVert4Half V0 = SrcLod.VertexBufferGPUSkin.VertsHalf[j]; FMeshUVHalf[] SrcUV = V0.UV; V = new FSkelMeshVertexBase { Infs = V0.Infs, Normal = V0.Normal, Pos = V0.Pos }; // UV: convert half -> float Lod.Verts[j].UV = (FMeshUVFloat)SrcUV[0]; for (int TexCoordIndex = 1; TexCoordIndex < NumTexCoords; TexCoordIndex++) { Lod.ExtraUV[TexCoordIndex - 1][j] = (FMeshUVFloat)SrcUV[TexCoordIndex]; } } else { FGPUVert4Float V0 = SrcLod.VertexBufferGPUSkin.VertsFloat[j]; FMeshUVFloat[] SrcUV = V0.UV; V = new FSkelMeshVertexBase { Infs = V0.Infs, Normal = V0.Normal, Pos = V0.Pos }; // UV: convert half -> float Lod.Verts[j].UV = SrcUV[0]; for (int TexCoordIndex = 1; TexCoordIndex < NumTexCoords; TexCoordIndex++) { Lod.ExtraUV[TexCoordIndex - 1][j] = SrcUV[TexCoordIndex]; } } Lod.Verts[j].Position = V.Pos; Lod.Verts[j].UnpackNormals(V.Normal); // convert influences Lod.Verts[j].Bone = new short[4]; int k2 = 0; uint PackedWeights = 0; for (int k = 0; k < 4; k++) { int BoneIndex = V.Infs.bone_index[k]; byte BoneWeight = V.Infs.bone_weight[k]; if (BoneWeight == 0) { continue; // skip this influence (but do not stop the loop!) } PackedWeights |= (uint)(BoneWeight << (k2 * 8)); Lod.Verts[j].Bone[k2] = (short)BoneMap[BoneIndex]; k2++; } Lod.Verts[j].PackedWeights = PackedWeights; if (k2 < 4) { Lod.Verts[j].Bone[k2] = -1; // mark end of list } } // indices Lod.Indices.Initialize(SrcLod.Indices.Indices16, SrcLod.Indices.Indices32); // sections Lod.Sections = new CMeshSection[SrcLod.Sections.Length]; FSkeletalMeshLODInfo Info = mesh.LODInfo[i]; for (int j = 0; j < SrcLod.Sections.Length; j++) { FSkelMeshSection S = SrcLod.Sections[j]; CMeshSection Dst = new CMeshSection(); // remap material for LOD int MaterialIndex = S.material_index; if (MaterialIndex >= 0 && MaterialIndex < Info.LODMaterialMap.Length) { MaterialIndex = Info.LODMaterialMap[MaterialIndex]; } if (S.material_index < mesh.Materials.Length) { Dst.Material = new UUnrealMaterial();// mesh.Materials[MaterialIndex].Material; } // -> TODO: actually get the object from the pak Dst.FirstIndex = (int)S.base_index; Dst.NumFaces = (int)S.num_triangles; Lod.Sections[j] = Dst; } Lods[i] = Lod; } // copy skeleton int NumBones = mesh.RefSkeleton.ref_bone_info.Length; RefSkeleton = new CSkelMeshBone[NumBones]; for (int i = 0; i < NumBones; i++) { FMeshBoneInfo B = mesh.RefSkeleton.ref_bone_info[i]; FTransform T = mesh.RefSkeleton.ref_bone_pose[i]; CSkelMeshBone Dst = new CSkelMeshBone { Name = B.name, ParentIndex = B.parent_index, Position = T.translation, Orientation = T.rotation }; // fix skeleton; all bones but 0 if (i >= 1) { Dst.Orientation.Conjugate(); } RefSkeleton[i] = Dst; } Sockets = null; // dunno where this is set FinalizeMesh(); }
internal USkeletalMesh(BinaryReader reader, FNameEntrySerialized[] name_map, FObjectImport[] import_map) { BaseObject = new UObject(reader, name_map, import_map, "SkeletalMesh", true); bool has_vertex_colors = false; foreach (var prop in BaseObject.properties) { if (prop.name == "bHasVertexColors" && prop.tag == FPropertyTagType.BoolProperty) { has_vertex_colors = (bool)prop.tag_data; } else if (prop.name == "LODInfo") { var data = ((UScriptArray)prop.tag_data).data; LODInfo = new FSkeletalMeshLODInfo[data.Length]; for (int i = 0; i < data.Length; i++) { var info = (UScriptStruct)data[i]; if (info.struct_name != "SkeletalMeshLODInfo") { throw new FileLoadException("Invalid lod info type"); } var props = ((FStructFallback)info.struct_type).properties; var newInfo = new FSkeletalMeshLODInfo(); foreach (var lodProp in props) { switch (lodProp.name) { case "DisplayFactor": newInfo.DisplayFactor = (float)lodProp.tag_data; break; case "LODHysteresis": newInfo.LODHysteresis = (float)lodProp.tag_data; break; case "LODMaterialMap": newInfo.LODMaterialMap = ((UScriptArray)lodProp.tag_data).data.Cast <int>().ToArray(); break; case "bEnableShadowCasting": newInfo.bEnableShadowCasting = ((UScriptArray)lodProp.tag_data).data.Cast <bool>().ToArray(); break; } } LODInfo[i] = newInfo; } } } var flags = new FStripDataFlags(reader); Bounds = new FBoxSphereBounds(reader); Materials = reader.ReadTArray(() => new FSkeletalMaterial(reader, name_map, import_map)); RefSkeleton = new FReferenceSkeleton(reader, name_map); if (!flags.editor_data_stripped) { Console.WriteLine("Editor data still present!"); } if (reader.ReadUInt32() == 0) { throw new FileLoadException("No cooked data"); } LODModels = reader.ReadTArray(() => new FStaticLODModel(reader, name_map, has_vertex_colors)); uint serialize_guid = reader.ReadUInt32(); MaterialAssets = new string[Materials.Length]; for (int i = 0; i < Materials.Length; i++) { if (Materials[i].Material.import == null) { continue; } for (int j = 0; j < import_map.Length; j++) { if (import_map[j].class_name != "MaterialInstanceConstant" && import_map[j].object_name.EndsWith(Materials[i].Material.import)) { MaterialAssets[i] = import_map[j].object_name; break; } } } }