static void Main(string[] args) { string basemdlname; if (args.Length > 0) { basemdlname = args[0]; } else { Console.Write("Base Model: "); basemdlname = Console.ReadLine().Trim('"'); } string fext = Path.GetExtension(basemdlname); ModelFile modelFile = new ModelFile(basemdlname); ModelFormat fmt = modelFile.Format; NJS_OBJECT basemdl = modelFile.Model; string mtnname; if (args.Length > 1) { mtnname = args[1]; } else { Console.Write("Motion: "); mtnname = Console.ReadLine().Trim('"'); } NJS_MOTION mtn = NJS_MOTION.Load(mtnname, basemdl.CountMorph()); string outdir; if (args.Length > 2) { outdir = args[2]; } else { outdir = Path.Combine(Path.GetDirectoryName(Path.GetFullPath(mtnname)), Path.GetFileNameWithoutExtension(mtnname)); } Directory.CreateDirectory(outdir); MTNInfo inf = new MTNInfo() { ModelFormat = fmt, Name = mtn.Name, Frames = mtn.Frames, InterpolationMode = mtn.InterpolationMode }; IniSerializer.Serialize(inf, Path.Combine(outdir, Path.ChangeExtension(Path.GetFileName(mtnname), ".ini"))); NJS_OBJECT[] objs = basemdl.GetObjects().Where(a => a.Morph).ToArray(); for (int frame = 0; frame < mtn.Frames; frame++) { if (!mtn.Models.Any(a => a.Value.Vertex.ContainsKey(frame))) { continue; } foreach ((int idx, AnimModelData amd) in mtn.Models) { if (amd.Vertex.ContainsKey(frame)) { Vertex[] verts = amd.Vertex[frame]; amd.Normal.TryGetValue(frame, out Vertex[] norms); switch (objs[idx].Attach) { case BasicAttach batt: for (int i = 0; i < Math.Min(verts.Length, batt.Vertex.Length); i++) { batt.Vertex[i] = verts[i]; if (norms != null && batt.Normal != null) { batt.Normal[i] = norms[i]; } } break; case ChunkAttach catt: if (catt.Vertex == null) { continue; } int vcnt = catt.Vertex.Sum(a => a.VertexCount); int ci = 0; int vi = 0; for (int i = 0; i < Math.Min(verts.Length, vcnt); i++) { catt.Vertex[ci].Vertices[vi] = verts[i]; if (norms != null && catt.Vertex[ci].Normals != null) { catt.Vertex[ci].Normals[vi] = norms[i]; } if (++vi >= catt.Vertex[ci].VertexCount) { ++ci; vi = 0; } } break; } } } ModelFile.CreateFile(Path.Combine(outdir, $"{frame}{fext}"), basemdl, null, null, null, null, fmt); } }
static void Main(string[] args) { string path; if (args.Length > 0) { path = args[0]; } else { Console.Write("Path: "); path = Console.ReadLine().Trim('"'); } path = Path.GetFullPath(path); if (path.EndsWith(".ini")) { path = Path.GetDirectoryName(path); } string mtnfn = new DirectoryInfo(path).Name; MTNInfo info = IniSerializer.Deserialize <MTNInfo>(Path.Combine(path, mtnfn + ".ini")); string fext = null; switch (info.ModelFormat) { case ModelFormat.Basic: case ModelFormat.BasicDX: fext = "*.sa1mdl"; break; case ModelFormat.Chunk: fext = "*.sa2mdl"; break; case ModelFormat.GC: fext = "*.sa2bmdl"; break; } SortedDictionary <int, NJS_OBJECT> mdls = new SortedDictionary <int, NJS_OBJECT>(); foreach (string fn in Directory.EnumerateFiles(path, fext)) { if (int.TryParse(Path.GetFileNameWithoutExtension(fn), out int i) && i >= 0 && i < info.Frames) { mdls[i] = new ModelFile(fn).Model; } } NJS_OBJECT first = mdls.First().Value; int nodecnt = first.CountMorph(); int[] vcnt = new int[nodecnt]; ushort[][] polys = new ushort[nodecnt][]; NJS_OBJECT[] nodes = first.GetObjects().Where(a => a.Morph).ToArray(); for (int i = 0; i < nodecnt; i++) { switch (nodes[i].Attach) { case BasicAttach batt: vcnt[i] = batt.Vertex.Length; List <ushort> plist = new List <ushort>(); foreach (NJS_MESHSET mesh in batt.Mesh) { foreach (Poly p in mesh.Poly) { plist.AddRange(p.Indexes); } } polys[i] = plist.ToArray(); break; case ChunkAttach catt: if (catt.Vertex != null) { vcnt[i] = catt.Vertex.Sum(a => a.VertexCount); } plist = new List <ushort>(); foreach (PolyChunkStrip pcs in catt.Poly.OfType <PolyChunkStrip>()) { foreach (PolyChunkStrip.Strip s in pcs.Strips) { plist.AddRange(s.Indexes); } } polys[i] = plist.ToArray(); break; } } NJS_MOTION mtn = new NJS_MOTION() { Name = info.Name, Frames = info.Frames, InterpolationMode = info.InterpolationMode, ModelParts = nodecnt }; foreach ((int frame, NJS_OBJECT mdl) in mdls) { NJS_OBJECT[] nodes2 = mdl.GetObjects().Where(a => a.Morph).ToArray(); if (nodes2.Length != nodecnt) { Console.WriteLine("Warning: Model {0} skeleton does not match first file!", frame); if (nodes2.Length > mtn.ModelParts) { mtn.ModelParts = nodes2.Length; } } for (int i = 0; i < nodes2.Length; i++) { switch (nodes2[i].Attach) { case BasicAttach batt: if (batt.Vertex.Length != vcnt[i]) { Console.WriteLine("Warning: Model {0} node {1} vertex count does not match first file!", frame, i); } AnimModelData amd; if (!mtn.Models.TryGetValue(i, out amd)) { amd = new AnimModelData() { VertexName = mtn.MdataName + "_vtx_" + i, NormalName = mtn.MdataName + "_nor_" + i }; mtn.Models[i] = amd; } amd.Vertex[frame] = batt.Vertex; if (batt.Normal != null) { amd.Normal[frame] = batt.Normal; } List <ushort> plist = new List <ushort>(); foreach (NJS_MESHSET mesh in batt.Mesh) { foreach (Poly p in mesh.Poly) { plist.AddRange(p.Indexes); } } if (!polys[i].SequenceEqual(plist)) { Console.WriteLine("Warning: Model {0} node {1} poly data does not match first file!", frame, i); } break; case ChunkAttach catt: if (catt.Vertex != null) { if (catt.Vertex.Sum(a => a.VertexCount) != vcnt[i]) { Console.WriteLine("Warning: Model {0} node {1} vertex count does not match first file!", frame, i); } if (!mtn.Models.TryGetValue(i, out amd)) { amd = new AnimModelData() { VertexName = mtn.MdataName + "_vtx_" + i, NormalName = mtn.MdataName + "_nor_" + i }; mtn.Models[i] = amd; } amd.Vertex[frame] = catt.Vertex.SelectMany(a => a.Vertices).ToArray(); if (catt.Vertex.All(a => a.Normals != null)) { amd.Normal[frame] = catt.Vertex.SelectMany(a => a.Normals).ToArray(); } } plist = new List <ushort>(); foreach (PolyChunkStrip pcs in catt.Poly.OfType <PolyChunkStrip>()) { foreach (PolyChunkStrip.Strip s in pcs.Strips) { plist.AddRange(s.Indexes); } } polys[i] = plist.ToArray(); break; } } } foreach ((int _, AnimModelData amd) in mtn.Models) { amd.VertexItemName = Enumerable.Range(0, amd.Vertex.Count).Select(a => amd.VertexName + "_" + a).ToArray(); if (amd.Normal.Count > 0) { amd.NormalItemName = Enumerable.Range(0, amd.Normal.Count).Select(a => amd.NormalName + "_" + a).ToArray(); } } mtn.Save(Path.Combine(path, $"{mtnfn}.saanim")); }