public static SKBitmap?Decode(this UTexture2D texture, FTexture2DMipMap?mip, ETexturePlatform platform = ETexturePlatform.DesktopMobile) { if (!texture.IsVirtual && mip != null) { byte[] data; SKColorType colorType; switch (platform) { case ETexturePlatform.Playstation: PlaystationDecoder.DecodeTexturePlaystation(mip, texture.Format, texture.isNormalMap, out data, out colorType); break; case ETexturePlatform.NintendoSwitch: NintendoSwitchDecoder.DecodeTextureNSW(mip, texture.Format, texture.isNormalMap, out data, out colorType); break; default: DecodeTexture(mip, texture.Format, texture.isNormalMap, out data, out colorType); break; } var width = mip.SizeX; var height = mip.SizeY; var info = new SKImageInfo(width, height, colorType, SKAlphaType.Unpremul); var bitmap = new SKBitmap(); unsafe { var pixelsPtr = NativeMemory.Alloc((nuint)data.Length); fixed(byte *p = data) { Unsafe.CopyBlockUnaligned(pixelsPtr, p, (uint)data.Length); } bitmap.InstallPixels(info, new IntPtr(pixelsPtr), info.RowBytes, (address, _) => NativeMemory.Free(address.ToPointer())); } if (!texture.bRenderNearestNeighbor) { return(bitmap); } var resized = bitmap.Resize(new SKImageInfo(width, height), SKFilterQuality.None); bitmap.Dispose(); return(resized); } return(null); }
public static SKBitmap?Decode(this UTexture2D texture, ETexturePlatform platform = ETexturePlatform.DesktopMobile) => texture.Decode(texture.GetFirstMip(), platform);
public MeshExporter(UStaticMesh 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; string ext; switch (meshFormat) { case EMeshFormat.ActorX: ext = "pskx"; ExportStaticMeshLods(lod, Ar, materialExports); break; case EMeshFormat.Gltf2: ext = "glb"; new Gltf(MeshName.SubstringAfterLast("/"), lod, materialExports).Save(meshFormat, Ar); break; case EMeshFormat.OBJ: ext = "obj"; new Gltf(MeshName.SubstringAfterLast("/"), lod, 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++; } }
private void ExportCommonMeshData(FArchiveWriter Ar, CMeshSection[] sections, CMeshVertex[] verts, FRawStaticIndexBuffer indices, CVertexShare share, List <MaterialExporter>?materialExports, ETexturePlatform platform = ETexturePlatform.DesktopMobile) { var mainHdr = new VChunkHeader(); var ptsHdr = new VChunkHeader(); var wedgHdr = new VChunkHeader(); var facesHdr = new VChunkHeader(); var matrHdr = new VChunkHeader(); var normHdr = new VChunkHeader(); mainHdr.TypeFlag = Constants.PSK_VERSION; Ar.SerializeChunkHeader(mainHdr, "ACTRHEAD"); var numPoints = share.Points.Count; ptsHdr.DataCount = numPoints; ptsHdr.DataSize = 12; Ar.SerializeChunkHeader(ptsHdr, "PNTS0000"); for (var i = 0; i < numPoints; i++) { var point = share.Points[i]; point.Y = -point.Y; // MIRROR_MESH point.Serialize(Ar); } var numFaces = 0; var numVerts = verts.Length; var numSections = sections.Length; var wedgeMat = new int[numVerts]; for (var i = 0; i < numSections; i++) { var faces = sections[i].NumFaces; numFaces += faces; for (var j = 0; j < faces * 3; j++) { wedgeMat[indices[j + sections[i].FirstIndex]] = i; } } wedgHdr.DataCount = numVerts; wedgHdr.DataSize = 16; Ar.SerializeChunkHeader(wedgHdr, "VTXW0000"); for (var i = 0; i < numVerts; i++) { Ar.Write(share.WedgeToVert[i]); Ar.Write(verts[i].UV.U); Ar.Write(verts[i].UV.V); Ar.Write((byte)wedgeMat[i]); Ar.Write((byte)0); Ar.Write((short)0); } facesHdr.DataCount = numFaces; if (numVerts <= 65536) { facesHdr.DataSize = 12; Ar.SerializeChunkHeader(facesHdr, "FACE0000"); for (var i = 0; i < numSections; i++) { for (var j = 0; j < sections[i].NumFaces; j++) { var wedgeIndex = new ushort[3]; for (var k = 0; k < wedgeIndex.Length; k++) { wedgeIndex[k] = (ushort)indices[sections[i].FirstIndex + j * 3 + k]; } Ar.Write(wedgeIndex[1]); // MIRROR_MESH Ar.Write(wedgeIndex[0]); // MIRROR_MESH Ar.Write(wedgeIndex[2]); Ar.Write((byte)i); Ar.Write((byte)0); Ar.Write((uint)1); } } } else { facesHdr.DataSize = 18; Ar.SerializeChunkHeader(facesHdr, "FACE3200"); for (var i = 0; i < numSections; i++) { for (var j = 0; j < sections[i].NumFaces; j++) { var wedgeIndex = new int[3]; for (var k = 0; k < wedgeIndex.Length; k++) { wedgeIndex[k] = indices[sections[i].FirstIndex + j * 3 + k]; } Ar.Write(wedgeIndex[1]); // MIRROR_MESH Ar.Write(wedgeIndex[0]); // MIRROR_MESH Ar.Write(wedgeIndex[2]); Ar.Write((byte)i); Ar.Write((byte)0); Ar.Write((uint)1); } } } matrHdr.DataCount = numSections; matrHdr.DataSize = 88; Ar.SerializeChunkHeader(matrHdr, "MATT0000"); for (var i = 0; i < numSections; i++) { string materialName; if (sections[i].Material?.Load <UMaterialInterface>() is { } tex) { materialName = tex.Name; materialExports?.Add(new MaterialExporter(tex, true, platform)); }
private void ExportSkeletalMeshLod(CSkelMeshLod lod, List <CSkelMeshBone> bones, FArchiveWriter Ar, List <MaterialExporter>?materialExports, ETexturePlatform platform = ETexturePlatform.DesktopMobile) { var share = new CVertexShare(); var infHdr = new VChunkHeader(); share.Prepare(lod.Verts); foreach (var vert in lod.Verts) { var weightsHash = vert.PackedWeights; for (var i = 0; i < vert.Bone.Length; i++) { weightsHash ^= (uint)vert.Bone[i] << i; } share.AddVertex(vert.Position, vert.Normal, weightsHash); } ExportCommonMeshData(Ar, lod.Sections.Value, lod.Verts, lod.Indices.Value, share, materialExports, platform); ExportSkeletonData(Ar, bones); var numInfluences = 0; for (var i = 0; i < share.Points.Count; i++) { for (var j = 0; j < Constants.NUM_INFLUENCES_UE4; j++) { if (lod.Verts[share.VertToWedge.Value[i]].Bone[j] < 0) { break; } numInfluences++; } } infHdr.DataCount = numInfluences; infHdr.DataSize = 12; Ar.SerializeChunkHeader(infHdr, "RAWWEIGHTS"); for (var i = 0; i < share.Points.Count; i++) { var v = lod.Verts[share.VertToWedge.Value[i]]; var unpackedWeights = v.UnpackWeights(); for (var j = 0; j < Constants.NUM_INFLUENCES_UE4; j++) { if (v.Bone[j] < 0) { break; } Ar.Write(unpackedWeights[j]); Ar.Write(i); Ar.Write((int)v.Bone[j]); } } ExportVertexColors(Ar, lod.VertexColors, lod.NumVerts); ExportExtraUV(Ar, lod.ExtraUV.Value, lod.NumVerts, lod.NumTexCoords); }
private void ExportStaticMeshLods(CStaticMeshLod lod, FArchiveWriter Ar, List <MaterialExporter>?materialExports, ETexturePlatform platform = ETexturePlatform.DesktopMobile) { var share = new CVertexShare(); var boneHdr = new VChunkHeader(); var infHdr = new VChunkHeader(); share.Prepare(lod.Verts); foreach (var vert in lod.Verts) { share.AddVertex(vert.Position, vert.Normal); } ExportCommonMeshData(Ar, lod.Sections.Value, lod.Verts, lod.Indices.Value, share, materialExports, platform); boneHdr.DataCount = 0; boneHdr.DataSize = 120; Ar.SerializeChunkHeader(boneHdr, "REFSKELT"); infHdr.DataCount = 0; infHdr.DataSize = 12; Ar.SerializeChunkHeader(infHdr, "RAWWEIGHTS"); ExportVertexColors(Ar, lod.VertexColors, lod.NumVerts); ExportExtraUV(Ar, lod.ExtraUV.Value, lod.NumVerts, lod.NumTexCoords); }
public MaterialExporter(UUnrealMaterial?unrealMaterial, bool bNoOtherTextures, ETexturePlatform platform = ETexturePlatform.DesktopMobile) : this() { if (unrealMaterial == null) { return; } _internalFilePath = unrealMaterial.Owner?.Name ?? unrealMaterial.Name; var allTextures = new List <UUnrealMaterial>(); unrealMaterial.AppendReferencedTextures(allTextures, false); var parameters = new CMaterialParams(); unrealMaterial.GetParams(parameters); if ((parameters.IsNull || parameters.Diffuse == unrealMaterial) && allTextures.Count == 0) { return; } var sb = new StringBuilder(); var toExport = new List <UUnrealMaterial>(); void Proc(string name, UUnrealMaterial?arg) { if (arg == null) { return; } sb.AppendLine($"{name}={arg.Name}"); switch (bNoOtherTextures) { case true when !name.StartsWith("Other["): case false: toExport.Add(arg); break; } } Proc("Diffuse", parameters.Diffuse); Proc("Normal", parameters.Normal); Proc("Specular", parameters.Specular); Proc("SpecPower", parameters.SpecPower); Proc("Opacity", parameters.Opacity); Proc("Emissive", parameters.Emissive); Proc("Cube", parameters.Cube); Proc("Mask", parameters.Mask); Proc("Misc", parameters.Misc); // Export other textures var numOtherTextures = 0; foreach (var texture in allTextures) { if (toExport.Contains(texture)) { continue; } Proc($"Other[{numOtherTextures++}]", texture); } _fileData = sb.ToString().Trim(); foreach (var texture in toExport) { if (texture == unrealMaterial || texture is not UTexture2D t) { continue; } _textures[t.Owner?.Name ?? t.Name] = t.Decode(platform); } if (!bNoOtherTextures && unrealMaterial is UMaterialInstanceConstant { Parent : { } } material) { _parentData = new MaterialExporter(material.Parent, bNoOtherTextures); } }