Skip to content

Instantly share code, notes, and snippets.

@CallumDev
Created March 20, 2021 11:20
Show Gist options
  • Save CallumDev/c17b23cc4b05ad3da1724e62eb5beb61 to your computer and use it in GitHub Desktop.
Save CallumDev/c17b23cc4b05ad3da1724e62eb5beb61 to your computer and use it in GitHub Desktop.
Program for creating a flattened vertex buffer from .obj and writing as a C header
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Text;
namespace Obj2Header
{
class Program
{
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.WriteLine("Usage Obj2Header input.obj output.h");
}
List<Vector3> positions = new List<Vector3>();
List<Vector3> normals = new List<Vector3>();
List<Vector2> texcoords = new List<Vector2>();
List<VertexRef> faces = new List<VertexRef>();
FillObj(args[0], positions, normals, texcoords, faces);
List<Vertex> vertices = new List<Vertex>();
List<int> indices = new List<int>();
bool useTex = false;
bool useNorm = false;
int reUseCount = 0;
foreach (var f in faces)
{
if (f.Normal > 0) useNorm = true;
if (f.Texture > 0) useTex = true;
bool found = false;
var p = positions[f.Pos - 1];
var nn = f.Normal > 0 ? normals[f.Normal - 1] : Vector3.Zero;
var t = f.Texture > 0 ? texcoords[f.Texture - 1] : Vector2.Zero;
for (int i = 0; i < vertices.Count; i++)
{
if (vertices[i].Position == p &&
vertices[i].Normal == nn &&
vertices[i].TexCoord == t)
{
indices.Add(i);
found = true;
reUseCount++;
break;
}
}
if (found) continue;
var vertex = new Vertex()
{
Position = p,
Normal = nn,
TexCoord = t,
Reference = f
};
indices.Add(vertices.Count);
vertices.Add(vertex);
}
var n = HeaderName(Path.GetFileNameWithoutExtension(args[1]));
var def = HeaderName(Path.GetFileName(args[1])).ToUpperInvariant() + "_";
string vTypeName = n + "Vertex";
using (var writer = new StreamWriter(File.Create(args[1])))
{
writer.WriteLine($"#ifndef {def}");
writer.WriteLine($"#define {def}");
writer.WriteLine("/* This file was generated by a tool */");
writer.WriteLine("/* Generated {0} vertices, {1} indices */", vertices.Count, indices.Count);
writer.WriteLine("/* Reused {0} merged vertices */", reUseCount);
writer.WriteLine("#include <stdint.h>");
writer.WriteLine("typedef struct {");
writer.WriteLine(" float x;");
writer.WriteLine(" float y;");
writer.WriteLine(" float z;");
if (useNorm)
{
writer.WriteLine(" float nx;");
writer.WriteLine(" float ny;");
writer.WriteLine(" float nz;");
}
if (useTex)
{
writer.WriteLine(" float u;");
writer.WriteLine(" float v;");
}
writer.WriteLine($"}} {vTypeName};");
writer.WriteLine($"extern {vTypeName} {n}Vertices[];");
writer.WriteLine($"extern int {n}VertexCount;");
writer.WriteLine($"extern uint16_t {n}Indices[];");
writer.WriteLine($"extern int {n}IndexCount;");
writer.WriteLine($"#ifdef {def}DEFINE_DATA");
writer.WriteLine($"{vTypeName} {n}Vertices[] = {{");
for(int i = 0; i < vertices.Count; i++)
{
var v = vertices[i];
writer.Write($" {{ {FloatStr(v.Position.X)}, {FloatStr(v.Position.Y)}, {FloatStr(v.Position.Z)}");
if (useNorm)
{
writer.Write($", {FloatStr(v.Normal.X)}, {FloatStr(v.Normal.Y)}, {FloatStr(v.Normal.Z)}");
}
if (useTex)
{
writer.Write($", {FloatStr(v.TexCoord.X)}, {FloatStr(v.TexCoord.Y)}");
}
if (i + 1 < vertices.Count)
writer.WriteLine(" },");
else
writer.WriteLine(" }");
}
writer.WriteLine("};");
writer.WriteLine($"int {n}VertexCount = sizeof({n}Vertices) / sizeof({vTypeName});");
writer.WriteLine($"uint16_t {n}Indices[] = {{");
writer.Write(" ");
writer.WriteLine(string.Join(',', indices.Select(x => x.ToString())));
writer.WriteLine("};");
writer.WriteLine($"int {n}IndexCount = sizeof({n}Indices) / sizeof(uint16_t);");
writer.WriteLine($"#endif");
writer.WriteLine($"#endif");
}
Console.WriteLine();
}
static string FloatStr(float f)
{
return f.ToString("F5");
}
static string HeaderName(string fn)
{
var builder = new StringBuilder();
if (char.IsDigit(fn[0]))
builder.Append('_');
for(int i = 0; i < fn.Length; i++)
{
char c = fn[i];
if ((c >= 0x30 && c <= 0x39) ||
(c >= 0x41 && c <= 0x5A) ||
(c >= 0x61 && c <= 0x7A) ||
(c == '_'))
{
builder.Append(c);
}
else
{
builder.Append('_');
}
}
return builder.ToString();
}
static string[] SplitLine(string s)
{
return s.Split(new[] {' ', '\t'}, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
}
static float GetF(string s)
{
return float.Parse(s, CultureInfo.InvariantCulture);
}
struct VertexRef
{
public int Pos;
public int Normal;
public int Texture;
}
struct Vertex
{
public Vector3 Position;
public Vector3 Normal;
public Vector2 TexCoord;
public VertexRef Reference;
}
static VertexRef ParseRef(string s)
{
var split = s.Split('/', StringSplitOptions.TrimEntries);
var r = new VertexRef();
r.Pos = int.Parse(split[0]);
if (split.Length > 1 && !string.IsNullOrWhiteSpace(split[1])) {
r.Texture = int.Parse(split[1]);
}
if (split.Length > 2) {
r.Normal = int.Parse(split[2]);
}
return r;
}
static void FillObj(string file, List<Vector3> p, List<Vector3> n, List<Vector2> uv, List<VertexRef> faces)
{
using (var reader = new StreamReader(file))
{
string ln;
bool hasObject = false;
while ((ln = reader.ReadLine()) != null)
{
ln = ln.Trim();
if (string.IsNullOrWhiteSpace(ln)) continue;
if (ln[0] == '#') continue;
if (ln.StartsWith("o ") || ln.StartsWith("g "))
{ //skip 2nd object
if (hasObject) return;
hasObject = true;
}
var nx = SplitLine(ln);
switch (nx[0])
{
case "v":
p.Add(new Vector3(GetF(nx[1]), GetF(nx[2]), GetF(nx[3])));
break;
case "vt":
uv.Add(new Vector2(GetF(nx[1]), GetF(nx[2])));
break;
case "vn":
n.Add(new Vector3(GetF(nx[1]), GetF(nx[2]), GetF(nx[3])));
break;
case "f":
for (int i = 2; i < (nx.Length - 1); i++) {
faces.Add(ParseRef(nx[1]));
faces.Add(ParseRef(nx[i]));
faces.Add(ParseRef(nx[i + 1]));
}
break;
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment