using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Xml; using Oni.Collections; using Oni.Metadata; using Oni.Sound; namespace Oni.Xml { internal sealed class XmlExporter : Exporter { private bool noAnimation; private bool recursive; private Totoro.Body animBody; private bool mergeAnimations; private Dae.Node animBodyNode; private Totoro.Animation mergedAnim; private string animDaeFileName; private readonly Dictionary externalChildren = new Dictionary(); private readonly Set queued = new Set(); private readonly Queue exportQueue = new Queue(); private InstanceDescriptor mainDescriptor; private string baseFileName; private XmlWriter xml; public XmlExporter(InstanceFileManager fileManager, string outputDirPath) : base(fileManager, outputDirPath) { } public bool NoAnimation { get { return noAnimation; } set { noAnimation = value; } } public bool Recursive { get { return recursive; } set { recursive = value; } } public Totoro.Body AnimationBody { get { return animBody; } set { animBody = value; animBodyNode = null; } } public bool MergeAnimations { get { return mergeAnimations; } set { mergeAnimations = value; } } protected override void ExportInstance(InstanceDescriptor descriptor) { exportQueue.Enqueue(descriptor); mainDescriptor = descriptor; string filePath = baseFileName = CreateFileName(descriptor, ".xml"); baseFileName = Path.GetFileNameWithoutExtension(filePath); if (recursive && animBody == null && descriptor.Template.Tag == TemplateTag.ONCC) animBody = Totoro.BodyDatReader.Read(descriptor); using (xml = CreateXmlWriter(filePath)) ExportDescriptors(xml); } private void ExportChild(InstanceDescriptor descriptor) { if (descriptor.Template.Tag == TemplateTag.TRCM && mainDescriptor.Template.Tag == TemplateTag.TRBS) { xml.WriteValue(WriteBody(descriptor)); return; } if (descriptor.Template.Tag == TemplateTag.M3GM) { if (!descriptor.IsPlaceholder) { xml.WriteValue(WriteGeometry(descriptor)); return; } if (recursive) { var m3gmFile = InstanceFileManager.FindInstance(descriptor.FullName, descriptor.File); if (m3gmFile != null && m3gmFile.Descriptors[0].Template.Tag == TemplateTag.M3GM && m3gmFile.Descriptors[0].Name == descriptor.Name) { xml.WriteValue(WriteGeometry(m3gmFile.Descriptors[0])); return; } } } if (!recursive || !descriptor.HasName) { if (descriptor.HasName) { xml.WriteValue(descriptor.FullName); } else { xml.WriteValue(string.Format(CultureInfo.InvariantCulture, "#{0}", descriptor.Index)); if (queued.Add(descriptor)) exportQueue.Enqueue(descriptor); } return; } var childFile = InstanceFileManager.FindInstance(descriptor.FullName, descriptor.File); if (childFile == null || childFile == mainDescriptor.File) { xml.WriteValue(descriptor.FullName); return; } string fileName; if (!externalChildren.TryGetValue(descriptor, out fileName)) { var exporter = new XmlExporter(InstanceFileManager, OutputDirPath) { recursive = recursive, animBody = animBody, mergeAnimations = mergeAnimations }; exporter.ExportFiles(new[] { childFile.FilePath }); fileName = Path.GetFileName(CreateFileName(descriptor, ".xml")); externalChildren.Add(descriptor, fileName); } xml.WriteValue(fileName); } private static XmlWriter CreateXmlWriter(string filePath) { var settings = new XmlWriterSettings { CloseOutput = true, Indent = true, IndentChars = " " }; var stream = File.Create(filePath); var writer = XmlWriter.Create(stream, settings); try { writer.WriteStartElement("Oni"); } catch { #if NETCORE writer.Dispose(); #else writer.Close(); #endif throw; } return writer; } private void ExportDescriptors(XmlWriter writer) { while (exportQueue.Count > 0) { var descriptor = exportQueue.Dequeue(); if (descriptor.IsPlaceholder || (descriptor.HasName && descriptor != mainDescriptor)) continue; switch (descriptor.Template.Tag) { case TemplateTag.TRAM: WriteAnimation(descriptor); break; case TemplateTag.BINA: WriteBinaryObject(descriptor); break; case TemplateTag.TXMP: // // Only export TXMP instances that have a name, the others // are animation frames and they're exported as part of named textures // if (descriptor.HasName) Oni.Motoko.TextureXmlExporter.Export(descriptor, writer, OutputDirPath, baseFileName); break; case TemplateTag.TXAN: // // Do nothing: TXAN instances are exported as part of TXMP instances // break; case TemplateTag.OSBD: WriteBinarySound(descriptor); break; default: GenericXmlWriter.Write(xml, ExportChild, descriptor); break; } } } private void WriteAnimation(InstanceDescriptor tram) { var anim = Totoro.AnimationDatReader.Read(tram); if (animBody == null) { Totoro.AnimationXmlWriter.Write(anim, xml, null, 0, 0); } else { if (animBodyNode == null) { var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath); var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter); var bodyWriter = new Totoro.BodyDaeWriter(geometryWriter); animBodyNode = bodyWriter.Write(animBody, false, null); } if (mergeAnimations) { if (mergedAnim == null) { mergedAnim = new Totoro.Animation(); animDaeFileName = tram.FullName + ".dae"; } var startFrame = mergedAnim.Heights.Count; Totoro.AnimationDaeWriter.AppendFrames(mergedAnim, anim); var endFrame = mergedAnim.Heights.Count; Totoro.AnimationXmlWriter.Write(anim, xml, animDaeFileName, startFrame, endFrame); } else { var fileName = tram.FullName + ".dae"; Totoro.AnimationDaeWriter.Write(animBodyNode, anim); Dae.Writer.WriteFile(Path.Combine(OutputDirPath, fileName), new Dae.Scene { Nodes = { animBodyNode } }); Totoro.AnimationXmlWriter.Write(anim, xml, fileName, 0, 0); } } } protected override void Flush() { if (mergedAnim != null) { Totoro.AnimationDaeWriter.Write(animBodyNode, mergedAnim); Dae.Writer.WriteFile(Path.Combine(OutputDirPath, animDaeFileName), new Dae.Scene { Nodes = { animBodyNode } }); mergedAnim = null; } } private string WriteBody(InstanceDescriptor descriptor) { string fileName; if (!externalChildren.TryGetValue(descriptor, out fileName)) { var body = Totoro.BodyDatReader.Read(descriptor); var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath); var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter); var bodyWriter = new Totoro.BodyDaeWriter(geometryWriter); var pelvis = bodyWriter.Write(body, noAnimation, null); fileName = string.Format("{0}_TRCM{1}.dae", mainDescriptor.FullName, descriptor.Index); Dae.Writer.WriteFile(Path.Combine(OutputDirPath, fileName), new Dae.Scene { Nodes = { pelvis } }); externalChildren.Add(descriptor, fileName); } return fileName; } private string WriteGeometry(InstanceDescriptor descriptor) { string fileName; if (!externalChildren.TryGetValue(descriptor, out fileName)) { var geometry = Motoko.GeometryDatReader.Read(descriptor); var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath); var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter); if (descriptor.HasName) fileName = descriptor.FullName + ".dae"; else fileName = string.Format("{0}_{1}.dae", mainDescriptor.Name, descriptor.Index); var node = geometryWriter.WriteNode(geometry, geometry.Name); Dae.Writer.WriteFile(Path.Combine(OutputDirPath, fileName), new Dae.Scene { Nodes = { node } }); externalChildren.Add(descriptor, fileName); } return fileName; } private void WriteBinarySound(InstanceDescriptor descriptor) { int dataSize, dataOffset; using (var reader = descriptor.OpenRead()) { dataSize = reader.ReadInt32(); dataOffset = reader.ReadInt32(); } using (var rawReader = descriptor.GetRawReader(dataOffset)) { OsbdXmlExporter.Export(rawReader, xml); } } private void WriteBinaryObject(InstanceDescriptor descriptor) { int dataSize, dataOffset; using (var reader = descriptor.OpenRead()) { dataSize = reader.ReadInt32(); dataOffset = reader.ReadInt32(); } using (var rawReader = descriptor.GetRawReader(dataOffset)) { var tag = (BinaryTag)rawReader.ReadInt32(); switch (tag) { case BinaryTag.OBJC: ObjcXmlExporter.Export(rawReader, xml); break; case BinaryTag.PAR3: ParticleXmlExporter.Export(descriptor.FullName.Substring(8), rawReader, xml); break; case BinaryTag.TMBD: TmbdXmlExporter.Export(rawReader, xml); break; case BinaryTag.ONIE: OnieXmlExporter.Export(rawReader, xml); break; case BinaryTag.SABD: SabdXmlExporter.Export(rawReader, xml); break; default: throw new NotSupportedException(string.Format("Unsupported BINA type '{0}'", Utils.TagToString((int)tag))); } } } } }