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(); }
static void ExportCommonMeshData(BinaryWriter writer, CMeshSection[] Sections, CSkelMeshVertex[] Verts, CIndexBuffer Indices, CVertexShare Share) { VChunkHeader MainHdr = new VChunkHeader(), PtsHdr, WedgHdr, FacesHdr, MatrHdr; int i; // main psk header SAVE_CHUNK(writer, MainHdr, "ACTRHEAD"); PtsHdr = new VChunkHeader { DataCount = Share.Points.Count, DataSize = 12 }; SAVE_CHUNK(writer, PtsHdr, "PNTS0000"); for (i = 0; i < Share.Points.Count; i++) { FVector V = Share.Points[i]; V.Y = -V.Y; V.Write(writer); } // get number of faces (some Gears3 meshes may have index buffer larger than needed) // get wedge-material mapping int numFaces = 0; int[] WedgeMat = new int[Verts.Length]; for (i = 0; i < Sections.Length; i++) { CMeshSection Sec = Sections[i]; numFaces += Sec.NumFaces; for (int j = 0; j < Sec.NumFaces * 3; j++) { WedgeMat[Indices[j + Sec.FirstIndex]] = i; } } WedgHdr = new VChunkHeader { DataCount = Verts.Length, DataSize = 16 }; SAVE_CHUNK(writer, WedgHdr, "VTXW0000"); for (i = 0; i < Verts.Length; i++) { CSkelMeshVertex S = Verts[i]; VVertex W = new VVertex { PointIndex = Share.WedgeToVert[i], U = S.UV.U, V = S.UV.V, MatIndex = (byte)WedgeMat[i], Reserved = 0, Pad = 0 }; W.Write(writer); } if (Verts.Length <= 65536) { FacesHdr = new VChunkHeader { DataCount = numFaces, DataSize = 12 }; SAVE_CHUNK(writer, FacesHdr, "FACE0000"); for (i = 0; i < Sections.Length; i++) { CMeshSection Sec = Sections[i]; for (int j = 0; j < Sec.NumFaces; j++) { VTriangle16 T = new VTriangle16 { WedgeIndex = new ushort[3] }; for (int k = 0; k < 3; k++) { int idx = (int)Indices[Sec.FirstIndex + j * 3 + k]; if (idx < 0 || idx >= 65536) { throw new FileLoadException("Invalid section index"); } T.WedgeIndex[k] = (ushort)idx; } T.MatIndex = (byte)i; T.AuxMatIndex = 0; T.SmoothingGroups = 1; ushort tmp = T.WedgeIndex[0]; T.WedgeIndex[0] = T.WedgeIndex[1]; T.WedgeIndex[1] = tmp; T.Write(writer); } } } else { // pskx extension FacesHdr = new VChunkHeader { DataCount = numFaces, DataSize = 18 }; SAVE_CHUNK(writer, FacesHdr, "FACE3200"); for (i = 0; i < Sections.Length; i++) { CMeshSection Sec = Sections[i]; for (int j = 0; j < Sec.NumFaces; j++) { VTriangle32 T = new VTriangle32 { WedgeIndex = new int[3] }; for (int k = 0; k < 3; k++) { int idx = (int)Indices[Sec.FirstIndex + j * 3 + k]; T.WedgeIndex[k] = idx; } T.MatIndex = (byte)i; T.AuxMatIndex = 0; T.SmoothingGroups = 1; int tmp = T.WedgeIndex[0]; T.WedgeIndex[0] = T.WedgeIndex[1]; T.WedgeIndex[1] = tmp; T.Write(writer); } } } MatrHdr = new VChunkHeader { DataCount = Sections.Length, DataSize = 88 }; SAVE_CHUNK(writer, MatrHdr, "MATT0000"); for (i = 0; i < Sections.Length; i++) { VMaterial M = new VMaterial { MaterialName = new byte[64] }; UUnrealMaterial Tex = Sections[i].Material; M.TextureIndex = i; // could be required for UT99 //!! this will not handle (UMaterialWithPolyFlags->Material==NULL) correctly - will make MaterialName=="None" //!! (the same valid for md5mesh export) Tex = null; if (Tex != null) { //Extensions.StrCpy(M.MaterialName, Tex.Name); //ExportObject(Tex); } else { Extensions.StrCpy(M.MaterialName, $"material_{i}"); } M.Write(writer); } }