using System; using System.IO; using System.Collections.Generic; using NW4RTools.Models; namespace NW4RTools { public class ObjWriter { public static void WriteModel(TextWriter tw, ResFile file, string modelName) { new ObjWriter(file).SaveModel(tw, modelName); } ResFile CurrentFile; Models.Model CurrentModel; TextWriter Output; private ObjWriter(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); 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) { // parse the 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(); PrimitiveType prim = (PrimitiveType)cmd; int vtxCount = dl.ReadUInt16(); Output.WriteLine("# Primitive: {0} ({1} vertices)", prim, vtxCount); // I wonder if INDEX8 is ever used here? // Going with INDEX16 for now. switch (prim) { case PrimitiveType.TriangleStrip: // De-stripify it! break; default: throw new NotImplementedException(); } } } } }