Skip to content

Instantly share code, notes, and snippets.

@valeIT
Forked from akderebur/Program.cs
Created September 16, 2018 20:50
Show Gist options
  • Save valeIT/9bf4284868fa57f65466a9661ec07ddb to your computer and use it in GitHub Desktop.
Save valeIT/9bf4284868fa57f65466a9661ec07ddb to your computer and use it in GitHub Desktop.
Wildstar M3 Model Load/Export
using System;
using System.IO;
using System.Text;
namespace M3Exporter
{
class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
return;
string file = args[0];
float divider = short.MaxValue;
FileStream fs = new FileStream(file, FileMode.Open);
BinaryReader reader = new BinaryReader(fs);
int magic = reader.ReadInt32();
if (magic != 1297040460) // LDOM
return;
fs.Seek(600, SeekOrigin.Begin);
long mTOff = reader.ReadInt64(); // mesh table offset
fs.Seek(1584 + mTOff, SeekOrigin.Begin); // 1584 = Header End
// Mesh Table
fs.Seek(24, SeekOrigin.Current);
int vertCount = reader.ReadInt32();
int blockLen = reader.ReadInt16();
fs.Seek(74, SeekOrigin.Current);
int indCount = reader.ReadInt32();
fs.Seek(12, SeekOrigin.Current);
long indOff = reader.ReadInt64();
long smCount = reader.ReadInt64(); // submeshes
long smTOff = reader.ReadInt64();
// Note data offsets
long vbStart = fs.Position + 64;
long indStart = vbStart + indOff;
long smStart = vbStart + smTOff;
// Help for arrays
int vInd = 0, uvInd = 0;
var meshes = new Submesh[smCount];
// Submesh Table
for (int s = 0; s < smCount; s++)
{
fs.Seek(smStart + 112 * s, SeekOrigin.Begin);
int startIndex = reader.ReadInt32();
int startVertex = reader.ReadInt32();
int sIndCount = reader.ReadInt32();
int sVertCount = reader.ReadInt32();
var sVertices = new float[sVertCount * 3];
var sUVs = new float[sVertCount * 2];
var sIndices = new int[sIndCount];
// Read mesh data
fs.Seek(vbStart + blockLen * startVertex, SeekOrigin.Begin);
for (int v = 0; v < sVertCount; v++)
{
vInd = v * 3;
uvInd = v * 2;
// Vertex position
sVertices[vInd] = reader.ReadInt16() / divider;
sVertices[vInd + 1] = reader.ReadInt16() / divider;
sVertices[vInd + 2] = reader.ReadInt16() / divider;
// UV
fs.Seek(blockLen - 10, SeekOrigin.Current); // assume uv always last 4 bytes
sUVs[uvInd] = Half.ToHalf(reader.ReadBytes(2), 0);
sUVs[uvInd + 1] = Half.ToHalf(reader.ReadBytes(2), 0);
}
fs.Seek(indStart + 2 * startIndex, SeekOrigin.Begin);
for (int i = 0; i < sIndCount; i++)
sIndices[i] = reader.ReadUInt16();
Submesh sm = new Submesh()
{
vertices = sVertices,
uvs = sUVs,
indices = sIndices
};
meshes[s] = sm;
}
reader.Close();
// Export meshes
ExportOBJ(meshes);
}
public static void ExportOBJ(Submesh[] meshes)
{
if (meshes == null || meshes.Length == 0)
return;
int startIndex = 0, mIndex = -1;
StringBuilder objS = new StringBuilder();
foreach (var mesh in meshes)
{
int vertCount = mesh.vertices.Length / 3;
int indCount = mesh.indices.Length;
objS.AppendLine("g mesh" + ++mIndex);
StringBuilder vertS = new StringBuilder();
StringBuilder uvS = new StringBuilder();
for (int i = 0; i < vertCount; i++)
{
int vInd = 3 * i;
int uvInd = 2 * i;
vertS.AppendLine(string.Format("v {0} {1} {2}", mesh.vertices[vInd], mesh.vertices[vInd + 1], mesh.vertices[vInd + 2]));
uvS.AppendLine(string.Format("vt {0} {1}", mesh.uvs[uvInd], (1f - mesh.uvs[uvInd + 1])));
}
objS.Append(vertS.ToString());
objS.Append(uvS.ToString());
// faces
for (int i = 0; i < indCount; i += 3)
{
objS.AppendLine(string.Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}", mesh.indices[i] + 1 + startIndex, mesh.indices[i + 1] + 1 + startIndex, mesh.indices[i + 2] + 1 + startIndex));
}
startIndex += vertCount;
}
File.WriteAllText("test.obj", objS.ToString());
}
public class Submesh
{
public float[] vertices { get; set; }
public float[] uvs { get; set; }
public int[] indices { get; set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment