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(); }
public void SortBones() { int NumBones = RefSkeleton.Length; int i; // prepare CBoneSortHelper if (NumBones >= 1024 * 3) { throw new FileLoadException("Too many bones"); } CBoneSortHelper helper = new CBoneSortHelper(); for (i = 0; i < NumBones; i++) { helper.Bones[i] = new CBoneSortHelper.Proxy { Bone = RefSkeleton[i], Parent = (i > 0) ? helper.Bones[RefSkeleton[i].ParentIndex] : null }; helper.SortedBones[i] = helper.Bones[i]; } // sort bones helper.SortBoneArray(NumBones); // build remap table int[] Remap = new int[1024 * 3]; int[] RemapBack = new int[1024 * 3]; for (i = 0; i < NumBones; i++) { CBoneSortHelper.Proxy P = helper.SortedBones[i]; int OldIndex = Array.IndexOf(helper.Bones, P); Remap[OldIndex] = i; RemapBack[i] = OldIndex; } // build new RefSkeleton CSkelMeshBone[] NewSkeleton = new CSkelMeshBone[NumBones]; for (i = 0; i < NumBones; i++) { NewSkeleton[i] = RefSkeleton[RemapBack[i]]; int oldParent = NewSkeleton[i].ParentIndex; NewSkeleton[i].ParentIndex = (oldParent > 0) ? Remap[oldParent] : 0; } Array.Copy(NewSkeleton, RefSkeleton, NumBones); // remap bone influences for (int lod = 0; lod < Lods.Length; lod++) { CSkelMeshLod L = Lods[lod]; int V = 0; for (i = 0; i < L.NumVerts; i++, V++) { for (int j = 0; j < 4; j++) { int Bone = L.Verts[V].Bone[j]; if (Bone < 0) { break; } L.Verts[V].Bone[j] = (short)Remap[Bone]; } } } }
public static void ExportMesh(BinaryWriter writer, CSkeletalMesh Mesh, CSkelMeshLod Lod) { VChunkHeader BoneHdr, InfHdr; int i, j; CVertexShare Share = new CVertexShare(); // weld vertices // The code below differs from similar code for StaticMesh export: it relies on vertex weight // information to not perform occasional welding of vertices which has the same position and // normal, but belongs to different bones. Share.Prepare(Lod.Verts, Lod.NumVerts, 48); for (i = 0; i < Lod.NumVerts; i++) { CSkelMeshVertex S = Lod.Verts[i]; // Here we relies on high possibility that vertices which should be shared between // triangles will have the same order of weights and bones (because most likely // these vertices were duplicated by copying). Doing more complicated comparison // will reduce performance with possibly reducing size of exported mesh by a few // more vertices. uint WeightsHash = S.PackedWeights; for (j = 0; j < S.Bone.Length; j++) { WeightsHash ^= (uint)(S.Bone[j] << j); } Share.AddVertex(S.Position, S.Normal, WeightsHash); } ExportCommonMeshData(writer, Lod.Sections, Lod.Verts, Lod.Indices, Share); int numBones = Mesh.RefSkeleton.Length; BoneHdr = new VChunkHeader { DataCount = numBones, DataSize = 120 }; SAVE_CHUNK(writer, BoneHdr, "REFSKELT"); for (i = 0; i < numBones; i++) { VBone B = new VBone { Name = new byte[64] }; CSkelMeshBone S = Mesh.RefSkeleton[i]; Extensions.StrCpy(B.Name, S.Name); // count NumChildren int NumChildren = 0; for (j = 0; j < numBones; j++) { if ((j != i) && (Mesh.RefSkeleton[j].ParentIndex == i)) { NumChildren++; } } B.NumChildren = NumChildren; B.ParentIndex = S.ParentIndex; B.BonePos.Position = S.Position; B.BonePos.Orientation = S.Orientation; B.BonePos.Orientation.Y *= -1; B.BonePos.Orientation.W *= -1; B.BonePos.Position.Y *= -1; B.Write(writer); } // count influences int NumInfluences = 0; for (i = 0; i < Share.Points.Count; i++) { int WedgeIndex = Share.VertToWedge[i]; CSkelMeshVertex V = Lod.Verts[WedgeIndex]; for (j = 0; j < 4; j++) { if (V.Bone[j] < 0) { break; } NumInfluences++; } } // write influences InfHdr = new VChunkHeader { DataCount = NumInfluences, DataSize = 12 }; SAVE_CHUNK(writer, InfHdr, "RAWWEIGHTS"); for (i = 0; i < Share.Points.Count; i++) { int WedgeIndex = Share.VertToWedge[i]; CSkelMeshVertex V = Lod.Verts[WedgeIndex]; CVec4 UnpackedWeights = V.UnpackWeights(); for (j = 0; j < 4; j++) { if (V.Bone[j] < 0) { break; } NumInfluences--; // just for verification VRawBoneInfluence I; I.Weight = UnpackedWeights.v[j]; I.BoneIndex = V.Bone[j]; I.PointIndex = i; I.Write(writer); } } if (NumInfluences != 0) { throw new FileLoadException("Did not write to all influences"); } ExportExtraUV(writer, Lod.ExtraUV, Lod.NumVerts, Lod.NumTexCoords); }