/// <summary> /// Creates a ModelPreviewMaterial that renders as close to what the given <see cref="Unreal.Classes.MaterialInstanceConstant"/> looks like as possible. /// </summary> /// <param name="texcache">The texture cache to request textures from.</param> /// <param name="mat">The material that this ModelPreviewMaterial will try to look like.</param> public ModelPreviewMaterial(PreviewTextureCache texcache, Unreal.Classes.MaterialInstanceConstant mat) { Properties.Add("Name", mat.pcc.Exports[mat.index].ObjectName); foreach (Unreal.Classes.MaterialInstanceConstant.TextureParam texparam in mat.Textures) { if (texparam.TexIndex != 0) { Textures.Add(texparam.Desc, FindTexture(texcache, mat.pcc.getEntry(texparam.TexIndex).GetFullPath, mat.pcc.FileName)); } } }
/// <summary> /// Creates a TexturedPreviewMaterial that renders as close to what the given <see cref="Unreal.Classes.MaterialInstanceConstant"/> looks like as possible. /// </summary> /// <param name="texcache">The texture cache to request textures from.</param> /// <param name="mat">The material that this ModelPreviewMaterial will try to look like.</param> public TexturedPreviewMaterial(PreviewTextureCache texcache, Unreal.Classes.MaterialInstanceConstant mat, List <PreloadedTextureData> preloadedTextures = null) : base(texcache, mat, preloadedTextures) { var matPackage = mat.Export.Parent.FullPath.ToLower(); foreach (var textureEntry in mat.Textures) { var texObjectName = textureEntry.FullPath.ToLower(); if (texObjectName.StartsWith(matPackage) && texObjectName.Contains("diff")) { // we have found the diffuse texture! DiffuseTextureFullName = textureEntry.FullPath; Debug.WriteLine("Diffuse texture of new material <" + Properties["Name"] + "> is " + DiffuseTextureFullName); return; } } foreach (var textureEntry in mat.Textures) { var texObjectName = textureEntry.ObjectName.Name.ToLower(); if (texObjectName.Contains("diff") || texObjectName.Contains("tex")) { // we have found the diffuse texture! DiffuseTextureFullName = textureEntry.FullPath; Debug.WriteLine("Diffuse texture of new material <" + Properties["Name"] + "> is " + DiffuseTextureFullName); return; } } foreach (var texparam in mat.Textures) { var texObjectName = texparam.ObjectName.Name.ToLower(); if (texObjectName.Contains("detail")) { // I guess a detail texture is good enough if we didn't return for a diffuse texture earlier... DiffuseTextureFullName = texparam.FullPath; Debug.WriteLine("Diffuse (Detail) texture of new material <" + Properties["Name"] + "> is " + DiffuseTextureFullName); return; } } foreach (var texparam in mat.Textures) { var texObjectName = texparam.ObjectName.Name.ToLower(); if (!texObjectName.Contains("norm") && !texObjectName.Contains("opac")) { //Anything is better than nothing I suppose DiffuseTextureFullName = texparam.FullPath; Debug.WriteLine("Using first found texture (last resort) of new material <" + Properties["Name"] + "> as diffuse: " + DiffuseTextureFullName); return; } } }
/// <summary> /// Creates a ModelPreviewMaterial that renders as close to what the given <see cref="Unreal.Classes.MaterialInstanceConstant"/> looks like as possible. /// </summary> /// <param name="texcache">The texture cache to request textures from.</param> /// <param name="mat">The material that this ModelPreviewMaterial will try to look like.</param> protected ModelPreviewMaterial(PreviewTextureCache texcache, Unreal.Classes.MaterialInstanceConstant mat, List <PreloadedTextureData> preloadedTextures = null) { if (mat == null) { return; } Properties.Add("Name", mat.Export.ObjectName); foreach (var textureEntry in mat.Textures) { if (!Textures.ContainsKey(textureEntry.FullPath) && textureEntry.ClassName == "Texture2D") //Apparently some assets are cubemaps, we don't want these. { if (preloadedTextures != null) { var preloadedInfo = preloadedTextures.FirstOrDefault(x => x.MaterialExport == mat.Export && x.Mip.Export.ObjectName.Name == textureEntry.ObjectName.Name); //i don't like matching on object name but its export vs import here. if (preloadedInfo != null) { Textures.Add(textureEntry.FullPath, texcache.LoadTexture(preloadedInfo.Mip.Export, preloadedInfo.Mip, preloadedInfo.decompressedTextureData)); } else { Debug.WriteLine("Preloading error"); } //if (textureEntry is ExportEntry texPort && preloadedMipInfo.Export != texPort) throw new Exception(); continue; //Don't further parse } if (textureEntry is ImportEntry import) { var extAsset = ModelPreview.FindExternalAsset(import, texcache.cache.Select(x => x.TextureExport).ToList()); if (extAsset != null) { Textures.Add(textureEntry.FullPath, texcache.LoadTexture(extAsset)); } } else { Textures.Add(textureEntry.FullPath, texcache.LoadTexture(textureEntry as ExportEntry)); } } } }
/// <summary> /// Creates a TexturedPreviewMaterial that renders as close to what the given <see cref="Unreal.Classes.MaterialInstanceConstant"/> looks like as possible. /// </summary> /// <param name="texcache">The texture cache to request textures from.</param> /// <param name="mat">The material that this ModelPreviewMaterial will try to look like.</param> public TexturedPreviewMaterial(PreviewTextureCache texcache, Unreal.Classes.MaterialInstanceConstant mat) : base(texcache, mat) { foreach (Unreal.Classes.MaterialInstanceConstant.TextureParam texparam in mat.Textures) { if (texparam.Desc.ToLower().Contains("diff") || texparam.Desc.ToLower().Contains("tex")) { // we have found the diffuse texture! DiffuseTextureFullName = texparam.Desc; //Console.WriteLine("Diffuse texture of new material <" + Properties["Name"] + "> is " + DiffuseTextureFullName); return; } } foreach (Unreal.Classes.MaterialInstanceConstant.TextureParam texparam in mat.Textures) { if (texparam.Desc.ToLower().Contains("detail")) { // I guess a detail texture is good enough if we didn't return for a diffuse texture earlier... DiffuseTextureFullName = texparam.Desc; //Console.WriteLine("Diffuse texture of new material <" + Properties["Name"] + "> is " + DiffuseTextureFullName); return; } } }
/// <summary> /// Creates a preview of the given <see cref="Unreal.Classes.SkeletalMesh"/>. /// </summary> /// <param name="Device">The Direct3D device to use for buffer creation.</param> /// <param name="m">The mesh to generate a preview for.</param> /// <param name="texcache">The texture cache for loading textures.</param> public ModelPreview(Device Device, Unreal.Classes.SkeletalMesh m, PreviewTextureCache texcache) { // STEP 1: MATERIALS for (int i = 0; i < m.Materials.Count; i++) { Unreal.Classes.MaterialInstanceConstant mat = m.MatInsts[i]; if (mat == null && m.Materials[i] < 0) { // The material instance is an import! ImportEntry matImport = m.Export.FileRef.GetImport(m.Materials[i]); var externalAsset = FindExternalAsset(matImport, texcache.cache.Select(x => x.TextureExport).ToList()); if (externalAsset != null) { mat = new MaterialInstanceConstant(externalAsset); } } if (mat != null) { ModelPreviewMaterial material; // TODO: pick what material class best fits based on what properties the // MaterialInstanceConstant mat has. // For now, just use the default material. material = new TexturedPreviewMaterial(texcache, mat); AddMaterial(material.Properties["Name"], material); } } // STEP 2: LODS foreach (Unreal.Classes.SkeletalMesh.LODModelStruct lodmodel in m.LODModels) { // Vertices List <WorldVertex> vertices = new List <WorldVertex>(); if (m.Export.Game == MEGame.ME1) { foreach (Unreal.Classes.SkeletalMesh.GPUSkinVertexStruct vertex in lodmodel.VertexBufferGPUSkin.Vertices) { vertices.Add(new WorldVertex(new Vector3(-vertex.Position.X, vertex.Position.Z, vertex.Position.Y), Vector3.Zero, new Vector2(vertex.UFullPrecision, vertex.VFullPrecision))); } } else { foreach (Unreal.Classes.SkeletalMesh.GPUSkinVertexStruct vertex in lodmodel.VertexBufferGPUSkin.Vertices) { // NOTE: note the switched Y and Z coordinates. Unreal seems to think that Z is up. vertices.Add(new WorldVertex(new Vector3(-vertex.Position.X, vertex.Position.Z, vertex.Position.Y), Vector3.Zero, new Vector2(HalfToFloat(vertex.U), HalfToFloat(vertex.V)))); } } // Triangles List <Triangle> triangles = new List <Triangle>(); for (int i = 0; i < lodmodel.IndexBuffer.Indexes.Count; i += 3) { triangles.Add(new Triangle(lodmodel.IndexBuffer.Indexes[i], lodmodel.IndexBuffer.Indexes[i + 1], lodmodel.IndexBuffer.Indexes[i + 2])); } WorldMesh mesh = new WorldMesh(Device, triangles, vertices); // Sections List <ModelPreviewSection> sections = new List <ModelPreviewSection>(); foreach (Unreal.Classes.SkeletalMesh.SectionStruct section in lodmodel.Sections) { if (section.MaterialIndex < Materials.Count) { sections.Add(new ModelPreviewSection(Materials.Keys.ElementAt(section.MaterialIndex), (uint)section.BaseIndex, (uint)section.NumTriangles)); } } LODs.Add(new ModelPreviewLOD(mesh, sections)); } }