1 |
using System; |
2 |
using System.Collections.Generic; |
3 |
using System.Globalization; |
4 |
using System.IO; |
5 |
using System.Xml; |
6 |
using Oni.Collections; |
7 |
using Oni.Metadata; |
8 |
using Oni.Sound; |
9 |
|
10 |
namespace Oni.Xml |
11 |
{ |
12 |
internal sealed class XmlExporter : Exporter |
13 |
{ |
14 |
private bool noAnimation; |
15 |
private bool recursive; |
16 |
private Totoro.Body animBody; |
17 |
private bool mergeAnimations; |
18 |
private Dae.Node animBodyNode; |
19 |
private Totoro.Animation mergedAnim; |
20 |
private string animDaeFileName; |
21 |
private readonly Dictionary<InstanceDescriptor, string> externalChildren = new Dictionary<InstanceDescriptor, string>(); |
22 |
private readonly Set<InstanceDescriptor> queued = new Set<InstanceDescriptor>(); |
23 |
private readonly Queue<InstanceDescriptor> exportQueue = new Queue<InstanceDescriptor>(); |
24 |
private InstanceDescriptor mainDescriptor; |
25 |
private string baseFileName; |
26 |
private XmlWriter xml; |
27 |
|
28 |
public XmlExporter(InstanceFileManager fileManager, string outputDirPath) |
29 |
: base(fileManager, outputDirPath) |
30 |
{ |
31 |
} |
32 |
|
33 |
public bool NoAnimation |
34 |
{ |
35 |
get { return noAnimation; } |
36 |
set { noAnimation = value; } |
37 |
} |
38 |
|
39 |
public bool Recursive |
40 |
{ |
41 |
get { return recursive; } |
42 |
set { recursive = value; } |
43 |
} |
44 |
|
45 |
public Totoro.Body AnimationBody |
46 |
{ |
47 |
get { return animBody; } |
48 |
set |
49 |
{ |
50 |
animBody = value; |
51 |
animBodyNode = null; |
52 |
} |
53 |
} |
54 |
|
55 |
public bool MergeAnimations |
56 |
{ |
57 |
get { return mergeAnimations; } |
58 |
set { mergeAnimations = value; } |
59 |
} |
60 |
|
61 |
protected override void ExportInstance(InstanceDescriptor descriptor) |
62 |
{ |
63 |
exportQueue.Enqueue(descriptor); |
64 |
|
65 |
mainDescriptor = descriptor; |
66 |
|
67 |
string filePath = baseFileName = CreateFileName(descriptor, ".xml"); |
68 |
baseFileName = Path.GetFileNameWithoutExtension(filePath); |
69 |
|
70 |
if (recursive && animBody == null && descriptor.Template.Tag == TemplateTag.ONCC) |
71 |
animBody = Totoro.BodyDatReader.Read(descriptor); |
72 |
|
73 |
using (xml = CreateXmlWriter(filePath)) |
74 |
ExportDescriptors(xml); |
75 |
} |
76 |
|
77 |
private void ExportChild(InstanceDescriptor descriptor) |
78 |
{ |
79 |
if (descriptor.Template.Tag == TemplateTag.TRCM && mainDescriptor.Template.Tag == TemplateTag.TRBS) |
80 |
{ |
81 |
xml.WriteValue(WriteBody(descriptor)); |
82 |
return; |
83 |
} |
84 |
|
85 |
if (descriptor.Template.Tag == TemplateTag.M3GM) |
86 |
{ |
87 |
if (!descriptor.IsPlaceholder) |
88 |
{ |
89 |
xml.WriteValue(WriteGeometry(descriptor)); |
90 |
return; |
91 |
} |
92 |
|
93 |
if (recursive) |
94 |
{ |
95 |
var m3gmFile = InstanceFileManager.FindInstance(descriptor.FullName, descriptor.File); |
96 |
|
97 |
if (m3gmFile != null && m3gmFile.Descriptors[0].Template.Tag == TemplateTag.M3GM && m3gmFile.Descriptors[0].Name == descriptor.Name) |
98 |
{ |
99 |
xml.WriteValue(WriteGeometry(m3gmFile.Descriptors[0])); |
100 |
return; |
101 |
} |
102 |
} |
103 |
} |
104 |
|
105 |
if (!recursive || !descriptor.HasName) |
106 |
{ |
107 |
if (descriptor.HasName) |
108 |
{ |
109 |
xml.WriteValue(descriptor.FullName); |
110 |
} |
111 |
else |
112 |
{ |
113 |
xml.WriteValue(string.Format(CultureInfo.InvariantCulture, "#{0}", descriptor.Index)); |
114 |
|
115 |
if (queued.Add(descriptor)) |
116 |
exportQueue.Enqueue(descriptor); |
117 |
} |
118 |
|
119 |
return; |
120 |
} |
121 |
|
122 |
var childFile = InstanceFileManager.FindInstance(descriptor.FullName, descriptor.File); |
123 |
|
124 |
if (childFile == null || childFile == mainDescriptor.File) |
125 |
{ |
126 |
xml.WriteValue(descriptor.FullName); |
127 |
return; |
128 |
} |
129 |
|
130 |
string fileName; |
131 |
|
132 |
if (!externalChildren.TryGetValue(descriptor, out fileName)) |
133 |
{ |
134 |
var exporter = new XmlExporter(InstanceFileManager, OutputDirPath) |
135 |
{ |
136 |
recursive = recursive, |
137 |
animBody = animBody, |
138 |
mergeAnimations = mergeAnimations |
139 |
}; |
140 |
|
141 |
exporter.ExportFiles(new[] { childFile.FilePath }); |
142 |
|
143 |
fileName = Path.GetFileName(CreateFileName(descriptor, ".xml")); |
144 |
|
145 |
externalChildren.Add(descriptor, fileName); |
146 |
} |
147 |
|
148 |
xml.WriteValue(fileName); |
149 |
} |
150 |
|
151 |
private static XmlWriter CreateXmlWriter(string filePath) |
152 |
{ |
153 |
var settings = new XmlWriterSettings |
154 |
{ |
155 |
CloseOutput = true, |
156 |
Indent = true, |
157 |
IndentChars = " " |
158 |
}; |
159 |
|
160 |
var stream = File.Create(filePath); |
161 |
var writer = XmlWriter.Create(stream, settings); |
162 |
|
163 |
try |
164 |
{ |
165 |
writer.WriteStartElement("Oni"); |
166 |
} |
167 |
catch |
168 |
{ |
169 |
#if NETCORE |
170 |
writer.Dispose(); |
171 |
#else |
172 |
writer.Close(); |
173 |
#endif |
174 |
throw; |
175 |
} |
176 |
|
177 |
return writer; |
178 |
} |
179 |
|
180 |
private void ExportDescriptors(XmlWriter writer) |
181 |
{ |
182 |
while (exportQueue.Count > 0) |
183 |
{ |
184 |
var descriptor = exportQueue.Dequeue(); |
185 |
|
186 |
if (descriptor.IsPlaceholder || (descriptor.HasName && descriptor != mainDescriptor)) |
187 |
continue; |
188 |
|
189 |
switch (descriptor.Template.Tag) |
190 |
{ |
191 |
case TemplateTag.TRAM: |
192 |
WriteAnimation(descriptor); |
193 |
break; |
194 |
|
195 |
case TemplateTag.BINA: |
196 |
WriteBinaryObject(descriptor); |
197 |
break; |
198 |
|
199 |
case TemplateTag.TXMP: |
200 |
// |
201 |
// Only export TXMP instances that have a name, the others |
202 |
// are animation frames and they're exported as part of named textures |
203 |
// |
204 |
|
205 |
if (descriptor.HasName) |
206 |
Oni.Motoko.TextureXmlExporter.Export(descriptor, writer, OutputDirPath, baseFileName); |
207 |
|
208 |
break; |
209 |
|
210 |
case TemplateTag.TXAN: |
211 |
// |
212 |
// Do nothing: TXAN instances are exported as part of TXMP instances |
213 |
// |
214 |
break; |
215 |
|
216 |
case TemplateTag.OSBD: |
217 |
WriteBinarySound(descriptor); |
218 |
break; |
219 |
|
220 |
default: |
221 |
GenericXmlWriter.Write(xml, ExportChild, descriptor); |
222 |
break; |
223 |
} |
224 |
} |
225 |
} |
226 |
|
227 |
private void WriteAnimation(InstanceDescriptor tram) |
228 |
{ |
229 |
var anim = Totoro.AnimationDatReader.Read(tram); |
230 |
|
231 |
if (animBody == null) |
232 |
{ |
233 |
Totoro.AnimationXmlWriter.Write(anim, xml, null, 0, 0); |
234 |
} |
235 |
else |
236 |
{ |
237 |
if (animBodyNode == null) |
238 |
{ |
239 |
var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath); |
240 |
var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter); |
241 |
var bodyWriter = new Totoro.BodyDaeWriter(geometryWriter); |
242 |
|
243 |
animBodyNode = bodyWriter.Write(animBody, false, null); |
244 |
} |
245 |
|
246 |
if (mergeAnimations) |
247 |
{ |
248 |
if (mergedAnim == null) |
249 |
{ |
250 |
mergedAnim = new Totoro.Animation(); |
251 |
animDaeFileName = tram.FullName + ".dae"; |
252 |
} |
253 |
|
254 |
var startFrame = mergedAnim.Heights.Count; |
255 |
Totoro.AnimationDaeWriter.AppendFrames(mergedAnim, anim); |
256 |
var endFrame = mergedAnim.Heights.Count; |
257 |
|
258 |
Totoro.AnimationXmlWriter.Write(anim, xml, animDaeFileName, startFrame, endFrame); |
259 |
} |
260 |
else |
261 |
{ |
262 |
var fileName = tram.FullName + ".dae"; |
263 |
|
264 |
Totoro.AnimationDaeWriter.Write(animBodyNode, anim); |
265 |
|
266 |
Dae.Writer.WriteFile(Path.Combine(OutputDirPath, fileName), new Dae.Scene |
267 |
{ |
268 |
Nodes = { animBodyNode } |
269 |
}); |
270 |
|
271 |
Totoro.AnimationXmlWriter.Write(anim, xml, fileName, 0, 0); |
272 |
} |
273 |
} |
274 |
} |
275 |
|
276 |
protected override void Flush() |
277 |
{ |
278 |
if (mergedAnim != null) |
279 |
{ |
280 |
Totoro.AnimationDaeWriter.Write(animBodyNode, mergedAnim); |
281 |
|
282 |
Dae.Writer.WriteFile(Path.Combine(OutputDirPath, animDaeFileName), new Dae.Scene |
283 |
{ |
284 |
Nodes = { animBodyNode } |
285 |
}); |
286 |
|
287 |
mergedAnim = null; |
288 |
} |
289 |
} |
290 |
|
291 |
private string WriteBody(InstanceDescriptor descriptor) |
292 |
{ |
293 |
string fileName; |
294 |
|
295 |
if (!externalChildren.TryGetValue(descriptor, out fileName)) |
296 |
{ |
297 |
var body = Totoro.BodyDatReader.Read(descriptor); |
298 |
|
299 |
var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath); |
300 |
var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter); |
301 |
var bodyWriter = new Totoro.BodyDaeWriter(geometryWriter); |
302 |
|
303 |
var pelvis = bodyWriter.Write(body, noAnimation, null); |
304 |
|
305 |
fileName = string.Format("{0}_TRCM{1}.dae", mainDescriptor.FullName, descriptor.Index); |
306 |
|
307 |
Dae.Writer.WriteFile(Path.Combine(OutputDirPath, fileName), new Dae.Scene |
308 |
{ |
309 |
Nodes = { pelvis } |
310 |
}); |
311 |
|
312 |
externalChildren.Add(descriptor, fileName); |
313 |
} |
314 |
|
315 |
return fileName; |
316 |
} |
317 |
|
318 |
private string WriteGeometry(InstanceDescriptor descriptor) |
319 |
{ |
320 |
string fileName; |
321 |
|
322 |
if (!externalChildren.TryGetValue(descriptor, out fileName)) |
323 |
{ |
324 |
var geometry = Motoko.GeometryDatReader.Read(descriptor); |
325 |
|
326 |
var textureWriter = new Motoko.TextureDaeWriter(OutputDirPath); |
327 |
var geometryWriter = new Motoko.GeometryDaeWriter(textureWriter); |
328 |
|
329 |
if (descriptor.HasName) |
330 |
fileName = descriptor.FullName + ".dae"; |
331 |
else |
332 |
fileName = string.Format("{0}_{1}.dae", mainDescriptor.Name, descriptor.Index); |
333 |
|
334 |
var node = geometryWriter.WriteNode(geometry, geometry.Name); |
335 |
|
336 |
Dae.Writer.WriteFile(Path.Combine(OutputDirPath, fileName), new Dae.Scene |
337 |
{ |
338 |
Nodes = { node } |
339 |
}); |
340 |
|
341 |
externalChildren.Add(descriptor, fileName); |
342 |
} |
343 |
|
344 |
return fileName; |
345 |
} |
346 |
|
347 |
private void WriteBinarySound(InstanceDescriptor descriptor) |
348 |
{ |
349 |
int dataSize, dataOffset; |
350 |
|
351 |
using (var reader = descriptor.OpenRead()) |
352 |
{ |
353 |
dataSize = reader.ReadInt32(); |
354 |
dataOffset = reader.ReadInt32(); |
355 |
} |
356 |
|
357 |
using (var rawReader = descriptor.GetRawReader(dataOffset)) |
358 |
{ |
359 |
OsbdXmlExporter.Export(rawReader, xml); |
360 |
} |
361 |
} |
362 |
|
363 |
private void WriteBinaryObject(InstanceDescriptor descriptor) |
364 |
{ |
365 |
int dataSize, dataOffset; |
366 |
|
367 |
using (var reader = descriptor.OpenRead()) |
368 |
{ |
369 |
dataSize = reader.ReadInt32(); |
370 |
dataOffset = reader.ReadInt32(); |
371 |
} |
372 |
|
373 |
using (var rawReader = descriptor.GetRawReader(dataOffset)) |
374 |
{ |
375 |
var tag = (BinaryTag)rawReader.ReadInt32(); |
376 |
|
377 |
switch (tag) |
378 |
{ |
379 |
case BinaryTag.OBJC: |
380 |
ObjcXmlExporter.Export(rawReader, xml); |
381 |
break; |
382 |
|
383 |
case BinaryTag.PAR3: |
384 |
ParticleXmlExporter.Export(descriptor.FullName.Substring(8), rawReader, xml); |
385 |
break; |
386 |
|
387 |
case BinaryTag.TMBD: |
388 |
TmbdXmlExporter.Export(rawReader, xml); |
389 |
break; |
390 |
|
391 |
case BinaryTag.ONIE: |
392 |
OnieXmlExporter.Export(rawReader, xml); |
393 |
break; |
394 |
|
395 |
case BinaryTag.SABD: |
396 |
SabdXmlExporter.Export(rawReader, xml); |
397 |
break; |
398 |
|
399 |
default: |
400 |
throw new NotSupportedException(string.Format("Unsupported BINA type '{0}'", Utils.TagToString((int)tag))); |
401 |
} |
402 |
} |
403 |
} |
404 |
} |
405 |
} |