Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simplified loader for creating a mesh from Wavefront OBJ (*.obj) format.
/*
* MIT License
*
* Copyright (c) 2020 Eric Freed
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
namespace Anvil
{
/// <summary>
/// Standard vertex definition, with a position, UV coordinates, and normal vector.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public readonly struct Vertex
{
/// <summary>
/// The vertex position.
/// </summary>
public readonly Vector3 Position;
/// <summary>
/// The texture coordinates (OpenGL style).
/// </summary>
public readonly Vector2 UV;
/// <summary>
/// The normal vector.
/// </summary>
public readonly Vector3 Normal;
/// <summary>
/// Creates a new instance of the <see cref="Vertex"/> structure.
/// </summary>
/// <param name="position">The position of the vertex in 3D space.</param>
/// <param name="uv">The texture UV coordinates.</param>
/// <param name="normal">The normal vector.</param>
public Vertex(Vector3 position, Vector2 uv, Vector3 normal)
{
Position = position;
UV = uv;
Normal = normal;
}
}
/// <summary>
/// A basic mesh class containing a collection of vertices and elements for creating a VBO and EBO/IBO.
/// </summary>
public class Mesh
{
private const string VERTEX = "v";
private const string UV = "vt";
private const string NORMAL = "vn";
private const string FACE = "f";
/// <summary>
/// An array of vertex data.
/// </summary>
public Vertex[] Vertices;
/// <summary>
/// An array of vertex indices, to use with <c>glDrawArrays</c>.
/// </summary>
public uint[] Elements;
/// <summary>
/// Creates a new instance of the <see cref="Mesh"/> class.
/// </summary>
/// <param name="vertices">A collection of vertex data.</param>
/// <param name="elements">A collection of corresponding indices for the vertices.</param>
public Mesh(IEnumerable<Vertex> vertices, IEnumerable<uint> elements)
{
Vertices = vertices.ToArray();
Elements = elements.ToArray();
}
/// <summary>
/// Loads a Wavefront OBJ file into a mesh.
/// <para>Assumes the data contains 3 component vectors for position and normal, and 2 component vector for texture coordinates.</para>
/// </summary>
/// <param name="filename">The path to the file.</param>
/// <returns>A newly created <see cref="Mesh"/>.</returns>
public static Mesh Load(string filename)
{
var positions = new List<Vector3>();
var uvs = new List<Vector2>();
var normals = new List<Vector3>();
var elements = new List<uint>();
Vector3 vec3 = default;
Vector2 vec2 = default;
var vertices = new List<Vertex>();
uint elementCount = 0;
using (var reader = new StreamReader(filename))
{
string line;
while ((line = reader.ReadLine()) != null)
{
var split = line.Split(' ');
if (split.Length == 0)
continue;
switch (split[0])
{
case VERTEX:
ParseVector3(split, ref vec3);
positions.Add(vec3);
break;
case UV:
vec2.X = float.Parse(split[1]);
vec2.Y = 1.0f - float.Parse(split[2]);
uvs.Add(vec2);
break;
case NORMAL:
ParseVector3(split, ref vec3);
normals.Add(vec3);
break;
case FACE:
for (var i = 1; i <= 3; i++)
{
var data = split[i].Split('/');
vertices.Add(new Vertex(
positions[int.Parse(data[0]) - 1],
uvs[int.Parse(data[1]) - 1],
normals[int.Parse(data[2]) - 1]
));
elements.Add(elementCount++);
}
break;
default:
continue;
}
}
}
return new Mesh(vertices, elements);
}
private static void ParseVector3(string[] parts, ref Vector3 vec3)
{
vec3.X = float.Parse(parts[1]);
vec3.Y = float.Parse(parts[2]);
vec3.Z = float.Parse(parts[3]);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.