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());