Skip to content

Commit

Permalink
Merge pull request #51 from charly-perspectives/master
Browse files Browse the repository at this point in the history
added support for meshes with diffuse AND normal map
  • Loading branch information
pierotofy authored Oct 6, 2023
2 parents 7ab8c4e + 9388606 commit bfdecb5
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 43 deletions.
12 changes: 12 additions & 0 deletions Obj2Gltf/Converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
6 changes: 6 additions & 0 deletions Obj2Gltf/Gltf/Material.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ public class Material
[JsonProperty("doubleSided")]
public bool DoubleSided { get; set; }

/// <summary>
/// The normal texture.
/// </summary>
[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() ?? "<null>"} {Name}";
}
Expand Down
4 changes: 4 additions & 0 deletions Obj2Gltf/WaveFront/Material.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public class Material
/// </summary>
public string AmbientTextureFile { get; set; }
/// <summary>
/// norm: Normal texture file path
/// </summary>
public string NormalTextureFile { get; set; }
/// <summary>
/// Ks: specular reflectivity of the current material
/// </summary>
public Reflectivity Specular { get; set; } = new Reflectivity(new FactorColor());
Expand Down
9 changes: 9 additions & 0 deletions Obj2Gltf/WaveFront/MtlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -195,6 +196,14 @@ public IEnumerable<Material> 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;
}
Expand Down
130 changes: 102 additions & 28 deletions Obj2Tiles.Library/Geometry/MeshT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}

Expand All @@ -443,15 +445,17 @@ private void LoadTexturesCache()
private void BinPackTextures(string targetFolder, int materialIndex, IReadOnlyList<List<int>> clusters,
IDictionary<Vertex2, int> newTextureVertices, ICollection<Task> 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,
Expand All @@ -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<Rgba32>(edgeLength, edgeLength);
var newTexture = !(material.Texture == null) ? new Image<Rgba32>(edgeLength, edgeLength) : null;
var newNormalMap = !(material.NormalMap == null) ? new Image<Rgba32>(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++)
Expand All @@ -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<Rgba32>(edgeLength, edgeLength);

newTexture = !(material.Texture == null) ? new Image<Rgba32>(edgeLength, edgeLength) : null;
newNormalMap = !(material.NormalMap == null) ? new Image<Rgba32>(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);

Expand All @@ -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;

Expand Down Expand Up @@ -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<Rgba32>;
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:
Expand All @@ -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<Rgba32>;
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,
Expand Down
28 changes: 23 additions & 5 deletions Obj2Tiles.Library/Materials/Material.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Material : ICloneable
{
public readonly string Name;
public string? Texture;
public string? NormalMap;

/// <summary>
/// Ka - Ambient
Expand Down Expand Up @@ -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;
Expand All @@ -57,6 +59,7 @@ public static Material[] ReadMtl(string path, out string[] dependencies)
var deps = new List<string>();

string texture = null;
string normalMap = null;
var name = string.Empty;
RGB? ambientColor = null, diffuseColor = null, specularColor = null;
double? specularExponent = null, dissolve = null;
Expand All @@ -66,14 +69,15 @@ 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":

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];
Expand All @@ -86,6 +90,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(
Expand Down Expand Up @@ -123,7 +135,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();
Expand All @@ -143,6 +155,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)
{
Expand Down Expand Up @@ -188,6 +205,7 @@ public object Clone()
return new Material(
Name,
Texture,
NormalMap,
AmbientColor,
DiffuseColor,
SpecularColor,
Expand Down
Loading

0 comments on commit bfdecb5

Please sign in to comment.