using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media;
namespace HelixToolkit
{
///
/// Obj (Wavefront) file reader
/// http://en.wikipedia.org/wiki/Obj
/// http://www.martinreddy.net/gfx/3d/OBJ.spec
/// http://www.eg-models.de/formats/Format_Obj.html
///
public class ObjReader : IModelReader
{
private StreamReader Reader { get; set; }
private Point3DCollection Points { get; set; }
private PointCollection TexCoords { get; set; }
private Vector3DCollection Normals { get; set; }
public string TexturePath { get; set; }
public class Group
{
public MeshBuilder MeshBuilder { get; set; }
public Material Material { get; set; }
public string Name { get; set; }
public int Smoothing { get; set; }
public Group(string name)
{
Name = name;
Material = MaterialHelper.CreateMaterial(Brushes.Green);
MeshBuilder = new MeshBuilder();
}
}
public class MaterialDefinition
{
// http://en.wikipedia.org/wiki/Material_Template_Library
public Color Ambient { get; set; }
public Color Diffuse { get; set; }
public Color Specular { get; set; }
public double SpecularCoefficient { get; set; }
public double Dissolved { get; set; }
public int Illumination { get; set; }
public string AmbientMap { get; set; }
public string AlphaMap { get; set; }
public string DiffuseMap { get; set; }
public string SpecularMap { get; set; }
public string BumpMap { get; set; }
public Material GetMaterial(string texturePath)
{
var mg = new MaterialGroup();
if (DiffuseMap == null)
{
var diffuseBrush = new SolidColorBrush(Diffuse) { Opacity = Dissolved };
mg.Children.Add(new DiffuseMaterial(diffuseBrush));
}
else
{
var path = Path.Combine(texturePath, DiffuseMap);
if (File.Exists(path))
{
var img = new BitmapImage(new Uri(path, UriKind.Relative));
var textureBrush = new ImageBrush(img) { Opacity = Dissolved };
mg.Children.Add(new DiffuseMaterial(textureBrush));
}
}
mg.Children.Add(new SpecularMaterial(new SolidColorBrush(Specular), SpecularCoefficient));
return mg;
}
}
public Dictionary Materials { get; private set; }
public Collection Groups { get; private set; }
public ObjReader()
{
Points = new Point3DCollection();
TexCoords = new PointCollection();
Normals = new Vector3DCollection();
Groups = new Collection();
Materials = new Dictionary();
}
public Model3DGroup Read(string path)
{
TexturePath = Path.GetDirectoryName(path);
var s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var result = Read(s);
s.Close();
return result;
}
///
/// Reads a GZipStream compressed OBJ file.
///
/// The path.
///
public Model3DGroup ReadZ(string path)
{
TexturePath = Path.GetDirectoryName(path);
var s = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var deflateStream = new GZipStream(s, CompressionMode.Decompress, true);
var result = Read(deflateStream);
deflateStream.Close();
s.Close();
return result;
}
public Model3DGroup Read(Stream s)
{
using (Reader = new StreamReader(s))
{
while (!Reader.EndOfStream)
{
var line = Reader.ReadLine();
if (line == null)
{
break;
}
line = line.Trim();
if (line.StartsWith("#") || line.Length == 0)
continue;
string id, values;
SplitLine(line, out id, out values);
switch (id.ToLower())
{
case "v":
AddVertex(values);
break;
case "vt":
AddTexCoord(values);
break;
case "vn":
AddNormal(values);
break;
case "f":
AddFace(values);
break;
case "g":
Groups.Add(new Group(values));
break;
case "mtllib":
LoadMaterialLib(values);
break;
case "usemtl":
SetMaterial(values);
break;
case "s":
SetSmoothing(values);
break;
case "o":
break;
}
}
}
return BuildModel();
}
Group CurrentGroup
{
get
{
if (Groups.Count == 0)
Groups.Add(new Group("default"));
return Groups[Groups.Count - 1];
}
}
private void SetSmoothing(string s)
{
int smoothing;
int.TryParse(s, out smoothing);
CurrentGroup.Smoothing = smoothing;
}
private void LoadMaterialLib(string mtlFile)
{
var path = Path.Combine(TexturePath, mtlFile);
if (!File.Exists(path))
return;
using (var mreader = new StreamReader(path))
{
MaterialDefinition currentMaterial = null;
while (!mreader.EndOfStream)
{
var line = mreader.ReadLine();
if (line == null)
{
break;
}
line = line.Trim();
if (line.StartsWith("#") || line.Length == 0)
continue;
string id, value;
SplitLine(line, out id, out value);
switch (id.ToLower())
{
case "newmtl":
if (value != null)
{
currentMaterial = new MaterialDefinition();
Materials.Add(value, currentMaterial);
}
break;
case "ka":
if (currentMaterial != null && value != null)
currentMaterial.Ambient = ColorParse(value);
break;
case "kd":
if (currentMaterial != null && value != null)
currentMaterial.Diffuse = ColorParse(value);
break;
case "ks":
if (currentMaterial != null && value != null)
currentMaterial.Specular = ColorParse(value);
break;
case "ns":
if (currentMaterial != null && value != null)
currentMaterial.SpecularCoefficient = DoubleParse(value);
break;
case "d":
if (currentMaterial != null && value != null)
currentMaterial.Dissolved = DoubleParse(value);
break;
//case "tr":
// if (currentMaterial != null)
// currentMaterial.Dissolved = DoubleParse(values);
// break;
case "illum":
if (currentMaterial != null && value!=null)
currentMaterial.Illumination = int.Parse(value);
break;
case "map_ka":
if (currentMaterial != null)
currentMaterial.AmbientMap = value;
break;
case "map_kd":
if (currentMaterial != null)
currentMaterial.DiffuseMap = value;
break;
case "map_ks":
if (currentMaterial != null)
currentMaterial.SpecularMap = value;
break;
case "map_d":
if (currentMaterial != null)
currentMaterial.AlphaMap = value;
break;
case "map_bump":
case "bump":
if (currentMaterial != null)
currentMaterial.BumpMap = value;
break;
}
}
}
}
private static Color ColorParse(string values)
{
var fields = Split(values);
return Color.FromRgb((byte)(fields[0] * 255), (byte)(fields[1] * 255), (byte)(fields[2] * 255));
}
private void SetMaterial(string materialName)
{
CurrentGroup.Material = GetMaterial(materialName);
}
private Material GetMaterial(string materialName)
{
MaterialDefinition mat;
if (Materials.TryGetValue(materialName, out mat))
{
return mat.GetMaterial(TexturePath);
}
return MaterialHelper.CreateMaterial(Brushes.Gold);
}
private void AddFace(string values)
{
// A polygonal face. The numbers are indexes into the arrays of vertex positions,
// texture coordinates, and normals respectively. A number may be omitted if,
// for example, texture coordinates are not being defined in the model.
// There is no maximum number of vertices that a single polygon may contain.
// The .obj file specification says that each face must be flat and convex.
var fields = values.Split(' ');
var pts = new Point3DCollection();
var tex = new PointCollection();
var norm = new Vector3DCollection();
foreach (var field in fields)
{
if (String.IsNullOrEmpty(field))
continue;
var ff = field.Split('/');
int vi = int.Parse(ff[0]);
int vti = ff.Length > 1 && ff[1].Length > 0 ? int.Parse(ff[1]) : -1;
int vni = ff.Length > 2 && ff[2].Length > 0 ? int.Parse(ff[2]) : -1;
pts.Add(Points[vi - 1]);
if (vti >= 0)
tex.Add(TexCoords[vti - 1]);
if (vni >= 0)
norm.Add(Normals[vni - 1]);
}
if (tex.Count == 0) tex = null;
if (norm.Count == 0) norm = null;
// QUAD
if (pts.Count == 4)
{
CurrentGroup.MeshBuilder.AddQuads(pts, norm, tex);
return;
}
// TRIANGLE
if (pts.Count == 3)
{
CurrentGroup.MeshBuilder.AddTriangles(pts, norm, tex);
return;
}
// POLYGONS (flat and convex)
var poly3D = new Polygon3D(pts);
// Transform the polygon to 2D
var poly2D = poly3D.Flatten();
// Triangulate
var tri = poly2D.Triangulate();
if (tri != null)
{
// Add the triangle indices with the 3D points
var mesh = new MeshBuilder();
mesh.Append(pts, tri);
CurrentGroup.MeshBuilder.Append(mesh);
}
}
private void AddNormal(string values)
{
var fields = Split(values);
Normals.Add(new Vector3D(fields[0], fields[1], fields[2]));
}
private void AddTexCoord(string values)
{
var fields = Split(values);
TexCoords.Add(new Point(fields[0], 1 - fields[1]));
}
private void AddVertex(string values)
{
var fields = Split(values);
Points.Add(new Point3D(fields[0], fields[1], fields[2]));
}
private static double[] Split(string values)
{
values = values.Trim();
var fields = values.Split(' ');
var result = new double[fields.Length];
for (int i = 0; i < fields.Length; i++)
result[i] = DoubleParse(fields[i]);
return result;
}
private static double DoubleParse(string s)
{
return double.Parse(s, CultureInfo.InvariantCulture);
}
private static void SplitLine(string line, out string id, out string values)
{
int idx = line.IndexOf(' ');
if (idx<0)
{
id = line;
values = null;
return;
}
id = line.Substring(0, idx);
values = line.Substring(idx + 1);
}
private Model3DGroup BuildModel()
{
var modelGroup = new Model3DGroup();
foreach (var g in Groups)
{
var gm = new GeometryModel3D
{
Geometry = g.MeshBuilder.ToMesh(),
Material = g.Material
};
gm.BackMaterial = gm.Material;
modelGroup.Children.Add(gm);
}
return modelGroup;
}
}
}