From c33b822237b9d6379407bfe09df54035e6d38a46 Mon Sep 17 00:00:00 2001 From: charly-perspectives <119584927+charly-perspectives@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:21:09 +0200 Subject: [PATCH 1/2] added support for meshes with diffuse AND normal map delete generated file --- Obj2Gltf/Converter.cs | 12 +++ Obj2Gltf/Gltf/Material.cs | 6 ++ Obj2Gltf/WaveFront/Material.cs | 4 + Obj2Gltf/WaveFront/MtlParser.cs | 9 ++ Obj2Tiles.Library/Geometry/MeshT.cs | 130 +++++++++++++++++++----- Obj2Tiles.Library/Materials/Material.cs | 23 ++++- Obj2Tiles/Utils.cs | 7 ++ 7 files changed, 160 insertions(+), 31 deletions(-) diff --git a/Obj2Gltf/Converter.cs b/Obj2Gltf/Converter.cs index 427f895..2b8b2a6 100644 --- a/Obj2Gltf/Converter.cs +++ b/Obj2Gltf/Converter.cs @@ -296,6 +296,18 @@ public static Gltf.Material ConvertMaterial(WaveFront.Material mat, GetOrAddText }; } + + var hasNormalTexture = !string.IsNullOrEmpty(mat.NormalTextureFile); + if (hasNormalTexture) + { + var index = getOrAddTextureFunction(mat.NormalTextureFile); + gMat.normalTexture = new TextureReferenceInfo + { + Index = index + }; + } + + if (mat.Emissive != null && mat.Emissive.Color != null) { gMat.EmissiveFactor = mat.Emissive.Color.ToArray(); diff --git a/Obj2Gltf/Gltf/Material.cs b/Obj2Gltf/Gltf/Material.cs index 4a0f9cc..a1ec881 100644 --- a/Obj2Gltf/Gltf/Material.cs +++ b/Obj2Gltf/Gltf/Material.cs @@ -61,6 +61,12 @@ public class Material [JsonProperty("doubleSided")] public bool DoubleSided { get; set; } + /// + /// The normal texture. + /// + [JsonProperty("normalTexture")] + public TextureReferenceInfo normalTexture { get; set; } + public override string ToString() => $"AM:{AlphaMode} DS:{(DoubleSided ? 1 : 0)} MRB:[{PbrMetallicRoughness.BaseColorFactor[0]}, {PbrMetallicRoughness.BaseColorFactor[1]}, {PbrMetallicRoughness.BaseColorFactor[2]}, {PbrMetallicRoughness.BaseColorFactor[3]}] E:[{EmissiveFactor[0]}, {EmissiveFactor[1]}, {EmissiveFactor[2]}] M:{PbrMetallicRoughness.MetallicFactor} R:{PbrMetallicRoughness.RoughnessFactor} T:{PbrMetallicRoughness.BaseColorTexture?.Index.ToString() ?? ""} {Name}"; } diff --git a/Obj2Gltf/WaveFront/Material.cs b/Obj2Gltf/WaveFront/Material.cs index f8bd497..0e40243 100644 --- a/Obj2Gltf/WaveFront/Material.cs +++ b/Obj2Gltf/WaveFront/Material.cs @@ -33,6 +33,10 @@ public class Material /// public string AmbientTextureFile { get; set; } /// + /// norm: Normal texture file path + /// + public string NormalTextureFile { get; set; } + /// /// Ks: specular reflectivity of the current material /// public Reflectivity Specular { get; set; } = new Reflectivity(new FactorColor()); diff --git a/Obj2Gltf/WaveFront/MtlParser.cs b/Obj2Gltf/WaveFront/MtlParser.cs index 505d9ca..a8cdf7b 100644 --- a/Obj2Gltf/WaveFront/MtlParser.cs +++ b/Obj2Gltf/WaveFront/MtlParser.cs @@ -19,6 +19,7 @@ public class MtlParser : IMtlParser private const string NsPrefix = "Ns"; private const string map_kaPrefix = "map_Ka"; private const string map_KdPrefix = "map_Kd"; + private const string normPrefix = "norm"; private static Reflectivity GetReflectivity(string val) { @@ -195,6 +196,14 @@ public IEnumerable Parse(Stream stream, string searchPath, Encoding en currentMaterial.DiffuseTextureFile = md; } } + else if (line.StartsWith(normPrefix)) + { + var mn = line.Substring(normPrefix.Length).Trim(); + if (File.Exists(Path.Combine(searchPath, mn))) + { + currentMaterial.NormalTextureFile = mn; + } + } } if (currentMaterial != null) yield return currentMaterial; } diff --git a/Obj2Tiles.Library/Geometry/MeshT.cs b/Obj2Tiles.Library/Geometry/MeshT.cs index a920a9b..b88afce 100644 --- a/Obj2Tiles.Library/Geometry/MeshT.cs +++ b/Obj2Tiles.Library/Geometry/MeshT.cs @@ -435,6 +435,8 @@ private void LoadTexturesCache() { if (!string.IsNullOrEmpty(material.Texture)) TexturesCache.GetTexture(material.Texture); + if (!string.IsNullOrEmpty(material.NormalMap)) + TexturesCache.GetTexture(material.NormalMap); }); } @@ -443,15 +445,17 @@ private void LoadTexturesCache() private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyList> clusters, IDictionary newTextureVertices, ICollection tasks) { + var material = _materials[materialIndex]; - if (material.Texture == null) + if (material.Texture == null && material.NormalMap == null) return; - var texture = TexturesCache.GetTexture(material.Texture); + var texture =!(material.Texture == null) ? TexturesCache.GetTexture(material.Texture) : null; + var normalMap = !(material.NormalMap == null) ? TexturesCache.GetTexture(material.NormalMap) : null; - var textureWidth = texture.Width; - var textureHeight = texture.Height; + var textureWidth = !(material.Texture == null) ? texture.Width : normalMap.Width; + var textureHeight = !(material.Texture == null) ? texture.Height : normalMap.Height; var clustersRects = clusters.Select(GetClusterRect).ToArray(); CalculateMaxMinAreaRect(clustersRects, textureWidth, textureHeight, out var maxWidth, out var maxHeight, @@ -472,9 +476,10 @@ private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyLi // NOTE: We could enable rotations but it would be a bit more complex var binPack = new MaxRectanglesBinPack(edgeLength, edgeLength, false); - var newTexture = new Image(edgeLength, edgeLength); + var newTexture = !(material.Texture == null) ? new Image(edgeLength, edgeLength) : null; + var newNormalMap = !(material.NormalMap == null) ? new Image(edgeLength, edgeLength) : null; - string? textureFileName, newPath; + string? textureFileName = null, normalMapFileName = null, newPathTexture = null, newPathNormalMap = null; var count = 0; for (var i = 0; i < clusters.Count; i++) @@ -498,22 +503,36 @@ private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyLi FreeRectangleChoiceHeuristic.RectangleBestAreaFit); if (newTextureClusterRect.Width == 0) - { + { Debug.WriteLine("Somehow we could not pack everything in the texture, splitting it in two"); - textureFileName = $"{Name}-texture-{material.Name}{Path.GetExtension(material.Texture)}"; - newPath = Path.Combine(targetFolder, textureFileName); - newTexture.Save(newPath); - newTexture.Dispose(); + textureFileName = !(material.Texture == null) ? $"{Name}-texture-diffuse-{material.Name}{Path.GetExtension(material.Texture)}" : null; + normalMapFileName = !(material.NormalMap == null) ? $"{Name}-texture-normal-{material.Name}{Path.GetExtension(material.NormalMap)}" : null; + if(!(material.Texture == null)) + { + newPathTexture = Path.Combine(targetFolder, textureFileName); + newTexture.Save(newPathTexture); + newTexture.Dispose(); + } + if(!(material.NormalMap == null)) + { + newPathNormalMap = Path.Combine(targetFolder, normalMapFileName); + newNormalMap.Save(newPathNormalMap); + newNormalMap.Dispose(); + } + - newTexture = new Image(edgeLength, edgeLength); + + newTexture = !(material.Texture == null) ? new Image(edgeLength, edgeLength) : null; + newNormalMap = !(material.NormalMap == null) ? new Image(edgeLength, edgeLength) : null; binPack = new MaxRectanglesBinPack(edgeLength, edgeLength, false); material.Texture = textureFileName; - + material.NormalMap = normalMapFileName; + // Avoid texture name collision count++; - material = new Material(material.Name + "-" + count, textureFileName, material.AmbientColor, + material = new Material(material.Name + "-" + count, textureFileName, normalMapFileName, material.AmbientColor, material.DiffuseColor, material.SpecularColor, material.SpecularExponent, material.Dissolve, material.IlluminationModel); @@ -532,12 +551,23 @@ private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyLi Debug.WriteLine("Found place for cluster at " + newTextureClusterRect); // Too long to explain this here, but it works - var adjustedSourceY = Math.Max(texture.Height - (clusterY + clusterHeight), 0); + var adjustedSourceY = !(material.Texture == null) + ? Math.Max(texture.Height - (clusterY + clusterHeight), 0) + : Math.Max(normalMap.Height - (clusterY + clusterHeight), 0); var adjustedDestY = Math.Max(edgeLength - (newTextureClusterRect.Y + clusterHeight), 0); - Common.CopyImage(texture, newTexture, clusterX, adjustedSourceY, clusterWidth, clusterHeight, - newTextureClusterRect.X, adjustedDestY); + if (!(material.Texture == null)) + { + Common.CopyImage(texture, newTexture, clusterX, adjustedSourceY, clusterWidth, clusterHeight, + newTextureClusterRect.X, adjustedDestY); + } + if (!(material.NormalMap == null)) + { + Common.CopyImage(normalMap, newNormalMap, clusterX, adjustedSourceY, clusterWidth, clusterHeight, + newTextureClusterRect.X, adjustedDestY); + } + var textureScaleX = (double)textureWidth / edgeLength; var textureScaleY = (double)textureHeight / edgeLength; @@ -585,23 +615,33 @@ private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyLi } } - textureFileName = TexturesStrategy == TexturesStrategy.Repack - ? $"{Name}-texture-{material.Name}{Path.GetExtension(material.Texture)}" - : $"{Name}-texture-{material.Name}.jpg"; + if (!(material.Texture == null)) + { + textureFileName = TexturesStrategy == TexturesStrategy.Repack + ? $"{Name}-texture-diffuse-{material.Name}{Path.GetExtension(material.Texture)}" + : $"{Name}-texture-diffuse-{material.Name}.jpg"; + newPathTexture = Path.Combine(targetFolder, textureFileName); + } - newPath = Path.Combine(targetFolder, textureFileName); + if (!(material.NormalMap == null)) + { + normalMapFileName = TexturesStrategy == TexturesStrategy.Repack + ? $"{Name}-texture-normal-{material.Name}{Path.GetExtension(material.NormalMap)}" + : $"{Name}-texture-normal-{material.Name}.jpg"; + newPathNormalMap = Path.Combine(targetFolder, normalMapFileName); + } - var saveTask = new Task(t => + var saveTaskTexture = new Task(t => { var tx = t as Image; switch (TexturesStrategy) { case TexturesStrategy.RepackCompressed: - tx.SaveAsJpeg(newPath, encoder); + tx.SaveAsJpeg(newPathTexture, encoder); break; case TexturesStrategy.Repack: - tx.Save(newPath); + tx.Save(newPathTexture); break; case TexturesStrategy.Compress: case TexturesStrategy.KeepOriginal: @@ -611,14 +651,48 @@ private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyLi throw new ArgumentOutOfRangeException(); } - Debug.WriteLine("Saved texture to " + newPath); + Debug.WriteLine("Saved texture to " + newPathTexture); tx.Dispose(); }, newTexture, TaskCreationOptions.LongRunning); - tasks.Add(saveTask); - saveTask.Start(); + var saveTaskNormalMap = new Task(t => + { + var tx = t as Image; - material.Texture = textureFileName; + switch (TexturesStrategy) + { + case TexturesStrategy.RepackCompressed: + tx.SaveAsJpeg(newPathNormalMap, encoder); + break; + case TexturesStrategy.Repack: + tx.Save(newPathNormalMap); + break; + case TexturesStrategy.Compress: + case TexturesStrategy.KeepOriginal: + throw new InvalidOperationException( + "KeepOriginal or Compress are meaningless here, we are repacking!"); + default: + throw new ArgumentOutOfRangeException(); + } + + Debug.WriteLine("Saved texture to " + newNormalMap); + tx.Dispose(); + }, newNormalMap, TaskCreationOptions.LongRunning); + + + if (!(material.Texture == null)) + { + tasks.Add(saveTaskTexture); + saveTaskTexture.Start(); + material.Texture = textureFileName; + } + + if (!(material.NormalMap == null)) + { + tasks.Add(saveTaskNormalMap); + saveTaskNormalMap.Start(); + material.NormalMap = normalMapFileName; + } } private void CalculateMaxMinAreaRect(RectangleF[] clustersRects, int textureWidth, int textureHeight, diff --git a/Obj2Tiles.Library/Materials/Material.cs b/Obj2Tiles.Library/Materials/Material.cs index 64d5286..84b6f82 100644 --- a/Obj2Tiles.Library/Materials/Material.cs +++ b/Obj2Tiles.Library/Materials/Material.cs @@ -8,6 +8,7 @@ public class Material : ICloneable { public readonly string Name; public string? Texture; + public string? NormalMap; /// /// Ka - Ambient @@ -36,12 +37,13 @@ public class Material : ICloneable public readonly IlluminationModel? IlluminationModel; - public Material(string name, string? texture = null, RGB? ambientColor = null, RGB? diffuseColor = null, + public Material(string name, string? texture = null, string? normalMap = null, RGB? ambientColor = null, RGB? diffuseColor = null, RGB? specularColor = null, double? specularExponent = null, double? dissolve = null, IlluminationModel? illuminationModel = null) { Name = name; Texture = texture; + NormalMap = normalMap; AmbientColor = ambientColor; DiffuseColor = diffuseColor; SpecularColor = specularColor; @@ -57,6 +59,7 @@ public static Material[] ReadMtl(string path, out string[] dependencies) var deps = new List(); string texture = null; + string normalMap = null; var name = string.Empty; RGB? ambientColor = null, diffuseColor = null, specularColor = null; double? specularExponent = null, dissolve = null; @@ -73,7 +76,7 @@ public static Material[] ReadMtl(string path, out string[] dependencies) case "newmtl": if (name.Length > 0) - materials.Add(new Material(name, texture, ambientColor, diffuseColor, specularColor, + materials.Add(new Material(name, texture, normalMap, ambientColor, diffuseColor, specularColor, specularExponent, dissolve, illuminationModel)); name = parts[1]; @@ -86,6 +89,14 @@ public static Material[] ReadMtl(string path, out string[] dependencies) deps.Add(texture); + break; + case "norm": + normalMap = Path.IsPathRooted(parts[1]) + ? parts[1] + : Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path)!, parts[1])); + + deps.Add(normalMap); + break; case "Ka": ambientColor = new RGB( @@ -123,7 +134,7 @@ public static Material[] ReadMtl(string path, out string[] dependencies) } } - materials.Add(new Material(name, texture, ambientColor, diffuseColor, specularColor, specularExponent, dissolve, + materials.Add(new Material(name, texture, normalMap, ambientColor, diffuseColor, specularColor, specularExponent, dissolve, illuminationModel)); dependencies = deps.ToArray(); @@ -143,6 +154,11 @@ public string ToMtl() builder.Append("map_Kd "); builder.AppendLine(Texture.Replace('\\', '/')); } + if (NormalMap != null) + { + builder.Append("norm "); + builder.AppendLine(NormalMap.Replace('\\', '/')); + } if (AmbientColor != null) { @@ -188,6 +204,7 @@ public object Clone() return new Material( Name, Texture, + NormalMap, AmbientColor, DiffuseColor, SpecularColor, diff --git a/Obj2Tiles/Utils.cs b/Obj2Tiles/Utils.cs index ee53589..dca59a4 100644 --- a/Obj2Tiles/Utils.cs +++ b/Obj2Tiles/Utils.cs @@ -52,6 +52,13 @@ private static IEnumerable GetMtlDependencies(string mtlPath) continue; } + if (line.StartsWith("norm")) + { + dependencies.Add(line[5..].Trim()); + + continue; + } + if (line.StartsWith("map_Ks")) { dependencies.Add(line[7..].Trim()); From 9388606d4db7e2c2c4614fae3ffb520bd14cc153 Mon Sep 17 00:00:00 2001 From: charly-perspectives <119584927+charly-perspectives@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:16:40 +0200 Subject: [PATCH 2/2] fix indentation in mtl file --- Obj2Tiles.Library/Materials/Material.cs | 5 +++-- Obj2Tiles/Utils.cs | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Obj2Tiles.Library/Materials/Material.cs b/Obj2Tiles.Library/Materials/Material.cs index 84b6f82..1e6f8d6 100644 --- a/Obj2Tiles.Library/Materials/Material.cs +++ b/Obj2Tiles.Library/Materials/Material.cs @@ -69,8 +69,9 @@ public static Material[] ReadMtl(string path, out string[] dependencies) { if (line.StartsWith("#") || string.IsNullOrWhiteSpace(line)) continue; - - var parts = line.Split(' '); + + var lineTrimmed = line.Trim(); + var parts = lineTrimmed.Split(' '); switch (parts[0]) { case "newmtl": diff --git a/Obj2Tiles/Utils.cs b/Obj2Tiles/Utils.cs index dca59a4..1df8bf3 100644 --- a/Obj2Tiles/Utils.cs +++ b/Obj2Tiles/Utils.cs @@ -35,73 +35,73 @@ private static IEnumerable GetMtlDependencies(string mtlPath) var dependencies = new List(); - + foreach (var line in mtlFile) { - if (line.StartsWith("map_Kd")) + if (line.Trim().StartsWith("map_Kd")) { dependencies.Add(line[7..].Trim()); continue; } - if (line.StartsWith("map_Ka")) + if (line.Trim().StartsWith("map_Ka")) { dependencies.Add(line[7..].Trim()); continue; } - if (line.StartsWith("norm")) + if (line.Trim().StartsWith("norm")) { dependencies.Add(line[5..].Trim()); continue; } - if (line.StartsWith("map_Ks")) + if (line.Trim().StartsWith("map_Ks")) { dependencies.Add(line[7..].Trim()); continue; } - if (line.StartsWith("map_Bump")) + if (line.Trim().StartsWith("map_Bump")) { dependencies.Add(line[8..].Trim()); continue; } - if (line.StartsWith("map_d")) + if (line.Trim().StartsWith("map_d")) { dependencies.Add(line[6..].Trim()); continue; } - if (line.StartsWith("map_Ns")) + if (line.Trim().StartsWith("map_Ns")) { dependencies.Add(line[7..].Trim()); continue; } - if (line.StartsWith("bump")) + if (line.Trim().StartsWith("bump")) { dependencies.Add(line[5..].Trim()); continue; } - if (line.StartsWith("disp")) + if (line.Trim().StartsWith("disp")) { dependencies.Add(line[5..].Trim()); continue; } - if (line.StartsWith("decal")) + if (line.Trim().StartsWith("decal")) { dependencies.Add(line[6..].Trim());