Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for meshes with diffuse AND normal map #51

Merged
merged 2 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading