public ModelInterface(USkeletalMesh mesh, int w, int h, Func <string, PakPackage> packageFunc) { this.w = w; this.h = h; this.mesh = mesh; this.packageFunc = packageFunc; }
internal UMeshSection(CSkeletalMesh mesh, USkeletalMesh umesh, int section, int[] disableOverrides, ModelInterface window, Func <string, PakPackage> packageFunc) { var cLod = mesh.Lods[LOD_LEVEL]; var uLod = umesh.LODModels[LOD_LEVEL]; var sec = uLod.Sections[section]; if (sec.disabled || Array.IndexOf(disableOverrides, sec.material_index) != -1) { Enabled = false; return; } texCoords = new Vector2[sec.num_vertices]; colorData = new Vector3[sec.num_vertices]; verts = new Vector3[sec.num_vertices]; normals = new Vector3[sec.num_vertices]; inds = new int[sec.num_triangles * 3]; int i; for (i = 0; i < sec.num_vertices; i++) { var v = cLod.Verts[i + sec.base_vertex_index]; var posV = v.Position.v; var norm = (FVector)(FPackedNormal)v.Normal; texCoords[i] = new Vector2(v.UV.U, 1 - v.UV.V); verts[i] = new Vector3(posV[0], posV[1], posV[2]); normals[i] = new Vector3(norm.X, norm.Y, norm.Z); } for (i = 0; i < sec.num_triangles * 3; i++) { inds[i] = (int)(cLod.Indices[(int)(i + sec.base_index)] - sec.base_vertex_index); } Material = GetMaterial(umesh.MaterialAssets[sec.material_index], window, packageFunc); TextureID = Material.DiffuseMap; }
public MeshExporter(USkeletalMesh originalMesh, ELodFormat lodFormat = ELodFormat.FirstLod, bool exportMaterials = true) { _meshName = originalMesh.Owner?.Name ?? originalMesh.Name; if (!originalMesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Length <= 0) { Log.Logger.Warning($"Mesh '{_meshName}' has no LODs"); _meshLods = new Mesh[0]; return; } _meshLods = new Mesh[lodFormat == ELodFormat.AllLods ? convertedMesh.LODs.Length : 1]; for (var i = 0; i < _meshLods.Length; i++) { if (convertedMesh.LODs[i].Sections.Value.Length <= 0 || convertedMesh.LODs[i].Indices.Value == null) { Log.Logger.Warning($"LOD {i} in mesh '{_meshName}' has no section"); continue; } var usePskx = convertedMesh.LODs[i].NumVerts > 65536; using var writer = new FCustomArchiveWriter(); var materialExports = exportMaterials ? new List <MaterialExporter>() : null; ExportSkeletalMeshLod(convertedMesh.LODs[i], convertedMesh.RefSkeleton, writer, materialExports); _meshLods[i] = new Mesh($"{_meshName}_LOD{i}.psk{(usePskx ? 'x' : "")}", writer.GetBuffer(), materialExports ?? new List <MaterialExporter>()); } }
public MeshExporter(USkeletalMesh originalMesh, ELodFormat lodFormat = ELodFormat.FirstLod, bool exportMaterials = true, EMeshFormat meshFormat = EMeshFormat.ActorX, ETexturePlatform platform = ETexturePlatform.DesktopMobile) { MeshLods = new List <Mesh>(); MeshName = originalMesh.Owner?.Name ?? originalMesh.Name; if (!originalMesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count == 0) { Log.Logger.Warning($"Mesh '{MeshName}' has no LODs"); return; } var i = 0; foreach (var lod in convertedMesh.LODs) { if (lod.SkipLod) { Log.Logger.Warning($"LOD {i} in mesh '{MeshName}' should be skipped"); continue; } using var Ar = new FArchiveWriter(); var materialExports = exportMaterials ? new List <MaterialExporter>() : null; var ext = ""; switch (meshFormat) { case EMeshFormat.ActorX: ext = convertedMesh.LODs[i].NumVerts > 65536 ? "pskx" : "psk"; ExportSkeletalMeshLod(lod, convertedMesh.RefSkeleton, Ar, materialExports, platform); break; case EMeshFormat.Gltf2: ext = "glb"; new Gltf(MeshName.SubstringAfterLast("/"), lod, convertedMesh.RefSkeleton, materialExports).Save(meshFormat, Ar); break; case EMeshFormat.OBJ: ext = "obj"; new Gltf(MeshName.SubstringAfterLast("/"), lod, convertedMesh.RefSkeleton, materialExports).Save(meshFormat, Ar); break; default: throw new ArgumentOutOfRangeException(nameof(meshFormat), meshFormat, null); } MeshLods.Add(new Mesh($"{MeshName}_LOD{i}.{ext}", Ar.GetBuffer(), materialExports ?? new List <MaterialExporter>())); if (lodFormat == ELodFormat.FirstLod) { break; } i++; } }
public static bool TryConvert(this USkeletalMesh originalMesh, out CSkeletalMesh convertedMesh) { convertedMesh = new CSkeletalMesh(); if (originalMesh.LODModels == null) { return(false); } convertedMesh.BoundingShere = new FSphere(0f, 0f, 0f, originalMesh.ImportedBounds.SphereRadius / 2); convertedMesh.BoundingBox = new FBox( originalMesh.ImportedBounds.Origin - originalMesh.ImportedBounds.BoxExtent, originalMesh.ImportedBounds.Origin + originalMesh.ImportedBounds.BoxExtent); var numLods = originalMesh.LODModels.Length; convertedMesh.LODs = new CSkelMeshLod[numLods]; for (var i = 0; i < convertedMesh.LODs.Length; i++) { if (originalMesh.LODModels[i] is not { Indices: not null } srcLod) { continue; } if (srcLod.Indices.Indices16.Length == 0 && srcLod.Indices.Indices32.Length == 0) { Log.Logger.Debug($"LOD {i} has no indices, skipping..."); continue; } var numTexCoords = srcLod.NumTexCoords; if (numTexCoords > _MAX_MESH_UV_SETS) { throw new ParserException($"Skeletal mesh has too many UV sets ({numTexCoords})"); } convertedMesh.LODs[i] = new CSkelMeshLod { NumTexCoords = numTexCoords, HasNormals = true, HasTangents = true, Indices = new Lazy <FRawStaticIndexBuffer>(() => new FRawStaticIndexBuffer { Indices16 = srcLod.Indices.Indices16, Indices32 = srcLod.Indices.Indices32 }), Sections = new Lazy <CMeshSection[]>(() => { var sections = new CMeshSection[srcLod.Sections.Length]; for (var j = 0; j < sections.Length; j++) { int materialIndex = srcLod.Sections[j].MaterialIndex; if (materialIndex < 0) // UE4 using Clamp(0, Materials.Num()), not Materials.Num()-1 { materialIndex = 0; } var m = materialIndex < originalMesh.Materials?.Length ? originalMesh.Materials[materialIndex].Material : null; sections[j] = new CMeshSection(m, srcLod.Sections[j].BaseIndex, srcLod.Sections[j].NumTriangles); } return(sections); }) }; var bUseVerticesFromSections = false; var vertexCount = srcLod.VertexBufferGPUSkin.GetVertexCount(); if (vertexCount == 0 && srcLod.Sections.Length > 0 && srcLod.Sections[0].SoftVertices.Length > 0) { bUseVerticesFromSections = true; for (var j = 0; j < srcLod.Sections.Length; j++) { vertexCount += srcLod.Sections[i].SoftVertices.Length; } } convertedMesh.LODs[i].AllocateVerts(vertexCount); var chunkIndex = -1; var chunkVertexIndex = 0; long lastChunkVertex = -1; ushort[]? boneMap = null; var vertBuffer = srcLod.VertexBufferGPUSkin; if (srcLod.ColorVertexBuffer.Data.Length == vertexCount) { convertedMesh.LODs[i].AllocateVertexColorBuffer(); } for (var vert = 0; vert < vertexCount; vert++) { while (vert >= lastChunkVertex) // this will fix any issues with empty chunks or sections { // proceed to next chunk or section if (srcLod.Chunks.Length > 0) { // pre-UE4.13 code: chunks var c = srcLod.Chunks[++chunkIndex]; lastChunkVertex = c.BaseVertexIndex + c.NumRigidVertices + c.NumSoftVertices; boneMap = c.BoneMap; } else { // UE4.13+ code: chunk information migrated to sections var s = srcLod.Sections[++chunkIndex]; lastChunkVertex = s.BaseVertexIndex + s.NumVertices; boneMap = s.BoneMap; } chunkVertexIndex = 0; } FSkelMeshVertexBase v; // has everything but UV[] if (bUseVerticesFromSections) { var v0 = srcLod.Sections[chunkIndex].SoftVertices[chunkVertexIndex++]; v = v0; // UV: simply copy float data convertedMesh.LODs[i].Verts[vert].UV = v0.UV[0]; for (var texCoordIndex = 1; texCoordIndex < numTexCoords; texCoordIndex++) { convertedMesh.LODs[i].ExtraUV.Value[texCoordIndex - 1][vert] = v0.UV[texCoordIndex]; } } else if (!vertBuffer.bUseFullPrecisionUVs) { var v0 = vertBuffer.VertsHalf[vert]; v = v0; // UV: convert half -> float convertedMesh.LODs[i].Verts[vert].UV = (FMeshUVFloat)v0.UV[0]; for (var texCoordIndex = 1; texCoordIndex < numTexCoords; texCoordIndex++) { convertedMesh.LODs[i].ExtraUV.Value[texCoordIndex - 1][vert] = (FMeshUVFloat)v0.UV[texCoordIndex]; } } else { var v0 = vertBuffer.VertsFloat[vert]; v = v0; // UV: simply copy float data convertedMesh.LODs[i].Verts[vert].UV = v0.UV[0]; for (var texCoordIndex = 1; texCoordIndex < numTexCoords; texCoordIndex++) { convertedMesh.LODs[i].ExtraUV.Value[texCoordIndex - 1][vert] = v0.UV[texCoordIndex]; } } convertedMesh.LODs[i].Verts[vert].Position = v.Pos; UnpackNormals(v.Normal, convertedMesh.LODs[i].Verts[vert]); if (convertedMesh.LODs[i].VertexColors != null) { convertedMesh.LODs[i].VertexColors[vert] = srcLod.ColorVertexBuffer.Data[vert]; } var i2 = 0; uint packedWeights = 0; for (var j = 0; j < 4; j++) { uint boneWeight = v.Infs.BoneWeight[j]; if (boneWeight == 0) { continue; // skip this influence (but do not stop the loop!) } packedWeights |= boneWeight << (i2 * 8); convertedMesh.LODs[i].Verts[vert].Bone[i2] = (short)boneMap[v.Infs.BoneIndex[j]]; i2++; } convertedMesh.LODs[i].Verts[vert].PackedWeights = packedWeights; if (i2 < 4) { convertedMesh.LODs[i].Verts[vert].Bone[i2] = -1; // mark end of list } } } var numBones = originalMesh.ReferenceSkeleton.FinalRefBoneInfo.Length; convertedMesh.RefSkeleton = new CSkelMeshBone[numBones]; for (var i = 0; i < convertedMesh.RefSkeleton.Length; i++) { convertedMesh.RefSkeleton[i] = new CSkelMeshBone { Name = originalMesh.ReferenceSkeleton.FinalRefBoneInfo[i].Name, ParentIndex = originalMesh.ReferenceSkeleton.FinalRefBoneInfo[i].ParentIndex, Position = originalMesh.ReferenceSkeleton.FinalRefBonePose[i].Translation, Orientation = originalMesh.ReferenceSkeleton.FinalRefBonePose[i].Rotation, }; // fix skeleton; all bones but 0 if (i >= 1) { convertedMesh.RefSkeleton[i].Orientation.Conjugate(); } } convertedMesh.FinalizeMesh(); return(true); }