using System; using System.IO; using System.Collections.Generic; using NW4RTools.Models; namespace NW4RTools { public class ObjExporter { public static void WriteModel(TextWriter tw, ResFile file, string modelName) { new ObjExporter(file).SaveModel(tw, modelName); } ResFile CurrentFile; Models.Model CurrentModel; TextWriter Output; private ObjExporter(ResFile file) { CurrentFile = file; } Dictionary VtxPosOffsets; Dictionary VtxNrmOffsets; Dictionary VtxTexCoordOffsets; public void SaveModel(TextWriter tw, string modelName) { Output = tw; CurrentModel = CurrentFile.GetGroup("3DModels(NW4R)")[modelName]; Output.WriteLine("# {0} exported by Treeki's NW4RTools", modelName); Output.WriteLine("# {0}", DateTime.Now); Output.WriteLine(); // Write vertex data pool int Offset; VtxPosOffsets = new Dictionary(); Offset = 1; foreach (var kv in CurrentModel.VtxPosData) { VtxPosOffsets[kv.Value] = Offset; Output.WriteLine("# Vertex Positions: {0} [offset {1}]", kv.Key, Offset); for (int i = 0; i < kv.Value.EntryCount; i++) { float[] v = kv.Value.GetEntry(i); if (kv.Value.ComponentCount == VertexSettings.CompCount.Position2) { Output.WriteLine("v {0} {1} 0.0", v[0], v[1]); } else { Output.WriteLine("v {0} {1} {2}", v[0], v[1], v[2]); } } Offset += kv.Value.EntryCount; } VtxNrmOffsets = new Dictionary(); Offset = 1; foreach (var kv in CurrentModel.VtxNrmData) { VtxNrmOffsets[kv.Value] = Offset; Output.WriteLine("# Vertex Normals: {0} [offset {1}]", kv.Key, Offset); for (int i = 0; i < kv.Value.EntryCount; i++) { float[] v = kv.Value.GetEntry(i); Output.WriteLine("vn {0} {1} {2}", v[0], v[1], v[2]); } Offset += kv.Value.EntryCount; } VtxTexCoordOffsets = new Dictionary(); Offset = 1; foreach (var kv in CurrentModel.VtxTexCoordData) { VtxTexCoordOffsets[kv.Value] = Offset; Output.WriteLine("# Vertex TexCoords: {0} [offset {1}]", kv.Key, Offset); for (int i = 0; i < kv.Value.EntryCount; i++) { float[] v = kv.Value.GetEntry(i); Output.WriteLine("vt {0} {1}", v[0], v[1]); } Offset += kv.Value.EntryCount; } Output.WriteLine(); // Write shapes // TODO: replace with something using the Bytecode foreach (var kv in CurrentModel.Shapes) { Output.WriteLine("g {0}", kv.Key); WriteShape(kv.Value); Output.WriteLine(); } Output.Flush(); } private void WriteShape(Models.Shape shape) { // first, parse the first DisplayList to get the attr info // for now we'll hardcode the offsets. must be fixed later var firstDL = new InputStream(shape.DisplayList1); firstDL.Seek(0x0C); UInt32 vtxDesc1 = firstDL.ReadUInt32(); firstDL.Seek(0x12); UInt32 vtxDesc2 = firstDL.ReadUInt32(); firstDL.Seek(0x22); UInt32 vtxAttr1 = firstDL.ReadUInt32(); firstDL.Seek(0x28); UInt32 vtxAttr2 = firstDL.ReadUInt32(); firstDL.Seek(0x2E); UInt32 vtxAttr3 = firstDL.ReadUInt32(); var vs = new VertexSettings(); vs.SetDesc(vtxDesc1, vtxDesc2); vs.SetAttrFmt(vtxAttr1, vtxAttr2, vtxAttr3); // get the Matrix to use // todo: how to apply this?! if (shape.MatrixID != -1) { Matrix m = CurrentModel.Nodes[CurrentModel.MatrixIDtoNodeID[shape.MatrixID]].NodeMatrix; } // now parse the second DisplayList int posOffset = VtxPosOffsets[shape.PosData]; int nrmOffset = shape.NrmData == null ? -1 : VtxNrmOffsets[shape.NrmData]; int tcOffset = shape.TexCoordData[0] == null ? -1 : VtxTexCoordOffsets[shape.TexCoordData[0]]; // TODO: Better DisplayList parsing, in a similar fashion to ByteCode var dl = new InputStream(shape.DisplayList2); while (true) { if (dl.AtEnd) break; byte cmd = dl.ReadByte(); if (cmd == 0) break; // These can contain other stuff. Who knew. if ((cmd & 0x80) == 0) { GXCommand castCmd = (GXCommand)cmd; switch (castCmd) { case GXCommand.LoadPosMtxFromArray: case GXCommand.LoadNrmMtxFromArray: case GXCommand.LoadTexCoordMtxFromArray: case GXCommand.LoadLightFromArray: dl.Skip(4); break; default: Console.WriteLine("UNIMPLEMENTED GX COMMAND: {0}", castCmd); break; } continue; // don't process it as a primitive } PrimitiveType prim = (PrimitiveType)((cmd >> 3) & 7); int vtxCount = dl.ReadUInt16(); Output.WriteLine("# Primitive: {0} ({1} vertices)", prim, vtxCount); // first, parse it into a list of vertices GXIndexedVertex[] vtxs = new GXIndexedVertex[vtxCount]; string[] pVtxs = new string[vtxCount]; // this is a little hackish, oh well string nrmPrefix = (vs.NormalDesc == VertexSettings.DescType.None) ? "" : "/"; for (int i = 0; i < vtxCount; i++) { vtxs[i].LoadFrom(dl, vs); string tc = (vtxs[i].TexCoords[0] == -1) ? "" : (tcOffset + vtxs[i].TexCoords[0]).ToString(); string n = (vtxs[i].Normal == -1) ? "" : "/" + (nrmOffset + vtxs[i].Normal).ToString(); pVtxs[i] = String.Format(" {0}/{1}{2}{3}", posOffset + vtxs[i].Position, tc, nrmPrefix, n); } switch (prim) { case PrimitiveType.Triangles: for (int i = 0; i < vtxCount; i += 3) { Output.WriteLine("f {0} {1} {2}", pVtxs[i], pVtxs[i + 1], pVtxs[i + 2]); } break; case PrimitiveType.TriangleStrip: // De-stripify it! for (int i = 2; i < vtxCount; i++) { Output.Write("f"); if ((i & 1) == 0) { // Even number Output.Write(pVtxs[i - 2]); Output.Write(pVtxs[i - 1]); Output.Write(pVtxs[i]); } else { // Odd number Output.Write(pVtxs[i - 1]); Output.Write(pVtxs[i - 2]); Output.Write(pVtxs[i]); } Output.WriteLine(); } break; default: Output.WriteLine("# UNIMPLEMENTED"); return; } } } } }