1 |
using System; |
2 |
using System.Collections.Generic; |
3 |
using System.IO; |
4 |
using System.Globalization; |
5 |
using System.Text; |
6 |
using System.Xml; |
7 |
|
8 |
namespace Oni.Dae.IO |
9 |
{ |
10 |
internal class DaeWriter |
11 |
{ |
12 |
#region Private data |
13 |
private XmlWriter xml; |
14 |
private Scene mainScene; |
15 |
private WriteVisitor visitor; |
16 |
private Dictionary<Source, string> writtenSources = new Dictionary<Source, string>(); |
17 |
#endregion |
18 |
|
19 |
#region private class Animation |
20 |
|
21 |
private class Animation : Entity |
22 |
{ |
23 |
public readonly List<Source> Sources = new List<Source>(); |
24 |
public readonly List<Sampler> Samplers = new List<Sampler>(); |
25 |
public readonly List<AnimationChannel> Channels = new List<AnimationChannel>(); |
26 |
} |
27 |
|
28 |
#endregion |
29 |
#region private class AnimationChannel |
30 |
|
31 |
private class AnimationChannel |
32 |
{ |
33 |
public readonly Sampler Sampler; |
34 |
public readonly string TargetPath; |
35 |
|
36 |
public AnimationChannel(Sampler sampler, string targetPath) |
37 |
{ |
38 |
this.Sampler = sampler; |
39 |
this.TargetPath = targetPath; |
40 |
} |
41 |
} |
42 |
|
43 |
#endregion |
44 |
#region private class AnimationSource |
45 |
|
46 |
private class AnimationSource : Source |
47 |
{ |
48 |
public readonly string[] Parameters; |
49 |
|
50 |
public AnimationSource(float[] data, string[] parameters) |
51 |
: base(data, parameters.Length) |
52 |
{ |
53 |
this.Parameters = parameters; |
54 |
} |
55 |
|
56 |
public AnimationSource(string[] data, string[] parameters) |
57 |
: base(data, parameters.Length) |
58 |
{ |
59 |
this.Parameters = parameters; |
60 |
} |
61 |
} |
62 |
|
63 |
#endregion |
64 |
|
65 |
#region private class WriteVisitor |
66 |
|
67 |
private class WriteVisitor : Visitor |
68 |
{ |
69 |
private readonly Dictionary<Entity, string> entities = new Dictionary<Entity, string>(); |
70 |
private readonly Dictionary<string, Entity> ids = new Dictionary<string, Entity>(StringComparer.Ordinal); |
71 |
private readonly Dictionary<string, Sampler> samplers = new Dictionary<string, Sampler>(StringComparer.Ordinal); |
72 |
private readonly Dictionary<string, Source> sources = new Dictionary<string, Source>(StringComparer.Ordinal); |
73 |
private int uniqueEntityId = 1; |
74 |
|
75 |
public readonly List<Image> Images = new List<Image>(); |
76 |
public readonly List<Effect> Effects = new List<Effect>(); |
77 |
public readonly List<Material> Materials = new List<Material>(); |
78 |
public readonly List<Geometry> Geometries = new List<Geometry>(); |
79 |
public readonly List<Scene> Scenes = new List<Scene>(); |
80 |
public readonly List<Animation> Animations = new List<Animation>(); |
81 |
public readonly List<Camera> Cameras = new List<Camera>(); |
82 |
|
83 |
public override void VisitScene(Scene scene) |
84 |
{ |
85 |
AddEntity(scene); |
86 |
|
87 |
base.VisitScene(scene); |
88 |
} |
89 |
|
90 |
public override void VisitNode(Node node) |
91 |
{ |
92 |
EnsureId(node); |
93 |
|
94 |
foreach (var transform in node.Transforms.Where(t => t.HasAnimations)) |
95 |
{ |
96 |
for (int i = 0; i < transform.Animations.Length; i++) |
97 |
{ |
98 |
var sampler = transform.Animations[i]; |
99 |
|
100 |
if (sampler != null) |
101 |
AddAnimationChannel(sampler, node, transform, i); |
102 |
} |
103 |
} |
104 |
|
105 |
base.VisitNode(node); |
106 |
} |
107 |
|
108 |
public override void VisitGeometry(Geometry geometry) |
109 |
{ |
110 |
AddEntity(geometry); |
111 |
|
112 |
string baseId = IdOf(geometry); |
113 |
|
114 |
if (baseId.EndsWith("_geometry", StringComparison.Ordinal)) |
115 |
baseId = baseId.Substring(0, baseId.Length - "_geometry".Length); |
116 |
|
117 |
foreach (var input in geometry.Vertices) |
118 |
EnsureId(input.Source, string.Format(CultureInfo.InvariantCulture, "{0}_{1}", baseId, input.Semantic.ToString().ToLowerInvariant())); |
119 |
|
120 |
foreach (var input in geometry.Primitives.SelectMany(p => p.Inputs)) |
121 |
EnsureId(input.Source, string.Format(CultureInfo.InvariantCulture, "{0}_{1}", baseId, input.Semantic.ToString().ToLowerInvariant())); |
122 |
|
123 |
base.VisitGeometry(geometry); |
124 |
} |
125 |
|
126 |
public override void VisitMaterial(Material material) |
127 |
{ |
128 |
AddEntity(material); |
129 |
|
130 |
base.VisitMaterial(material); |
131 |
} |
132 |
|
133 |
public override void VisitEffect(Effect effect) |
134 |
{ |
135 |
AddEntity(effect); |
136 |
|
137 |
base.VisitEffect(effect); |
138 |
} |
139 |
|
140 |
public override void VisitImage(Image image) |
141 |
{ |
142 |
AddEntity(image); |
143 |
|
144 |
base.VisitImage(image); |
145 |
} |
146 |
|
147 |
public override void VisitCamera(Camera camera) |
148 |
{ |
149 |
AddEntity(camera); |
150 |
|
151 |
base.VisitCamera(camera); |
152 |
} |
153 |
|
154 |
private void AddEntity(Scene scene) |
155 |
{ |
156 |
AddEntity(scene, Scenes); |
157 |
} |
158 |
|
159 |
private void AddEntity(Image image) |
160 |
{ |
161 |
AddEntity(image, Images); |
162 |
} |
163 |
|
164 |
private void AddEntity(Effect effect) |
165 |
{ |
166 |
AddEntity(effect, Effects); |
167 |
} |
168 |
|
169 |
private void AddEntity(Material material) |
170 |
{ |
171 |
AddEntity(material, Materials); |
172 |
} |
173 |
|
174 |
private void AddEntity(Geometry geometry) |
175 |
{ |
176 |
AddEntity(geometry, Geometries); |
177 |
} |
178 |
|
179 |
private void AddEntity(Animation animation) |
180 |
{ |
181 |
AddEntity(animation, Animations); |
182 |
} |
183 |
|
184 |
private void AddEntity(Camera camera) |
185 |
{ |
186 |
AddEntity(camera, Cameras); |
187 |
} |
188 |
|
189 |
private void AddEntity<T>(T entity, ICollection<T> entityCollection) |
190 |
where T : Entity |
191 |
{ |
192 |
if (EnsureId(entity)) |
193 |
entityCollection.Add(entity); |
194 |
} |
195 |
|
196 |
private bool EnsureId(Entity entity) |
197 |
{ |
198 |
if (entities.ContainsKey(entity)) |
199 |
return false; |
200 |
|
201 |
string name = entity.Name; |
202 |
string id; |
203 |
|
204 |
if (string.IsNullOrEmpty(name)) |
205 |
{ |
206 |
do |
207 |
{ |
208 |
id = string.Format(CultureInfo.InvariantCulture, |
209 |
"unique_{0}", uniqueEntityId++, entity.GetType().Name.ToLowerInvariant()); |
210 |
} |
211 |
while (ids.ContainsKey(id)); |
212 |
} |
213 |
else |
214 |
{ |
215 |
if (!ids.ContainsKey(name)) |
216 |
{ |
217 |
id = name; |
218 |
} |
219 |
else |
220 |
{ |
221 |
id = string.Format(CultureInfo.InvariantCulture, |
222 |
"{0}_{1}", name, entity.GetType().Name.ToLowerInvariant()); |
223 |
|
224 |
while (ids.ContainsKey(id)) |
225 |
{ |
226 |
id = string.Format(CultureInfo.InvariantCulture, |
227 |
"{0}_{1}_{2}", name, uniqueEntityId++, entity.GetType().Name.ToLowerInvariant()); |
228 |
} |
229 |
} |
230 |
} |
231 |
|
232 |
entities.Add(entity, id); |
233 |
ids.Add(id, entity); |
234 |
|
235 |
return true; |
236 |
} |
237 |
|
238 |
private bool EnsureId(Entity entity, string id) |
239 |
{ |
240 |
if (entities.ContainsKey(entity)) |
241 |
return false; |
242 |
|
243 |
entities.Add(entity, id); |
244 |
ids.Add(id, entity); |
245 |
|
246 |
return true; |
247 |
} |
248 |
|
249 |
public string IdOf(Entity entity) |
250 |
{ |
251 |
string id; |
252 |
entities.TryGetValue(entity, out id); |
253 |
return id; |
254 |
} |
255 |
|
256 |
public string UrlOf(Entity entity) |
257 |
{ |
258 |
return string.Format("#{0}", IdOf(entity)); |
259 |
} |
260 |
|
261 |
private void AddAnimationChannel(Sampler sampler, Node node, Transform transform, int valueIndex) |
262 |
{ |
263 |
Animation animation; |
264 |
|
265 |
if (Animations.Count == 0) |
266 |
{ |
267 |
animation = new Animation(); |
268 |
Animations.Add(animation); |
269 |
} |
270 |
else |
271 |
{ |
272 |
animation = Animations[0]; |
273 |
} |
274 |
|
275 |
EnsureId(sampler); |
276 |
|
277 |
string nodeId = IdOf(node); |
278 |
string samplerId = IdOf(sampler); |
279 |
string valueName = transform.ValueIndexToValueName(valueIndex); |
280 |
Sampler valueSampler; |
281 |
|
282 |
if (!samplers.TryGetValue(samplerId + valueName, out valueSampler)) |
283 |
{ |
284 |
valueSampler = new Sampler(); |
285 |
EnsureId(valueSampler, string.Format("{0}_{1}_{2}", IdOf(node), transform.Sid, valueName)); |
286 |
animation.Samplers.Add(valueSampler); |
287 |
|
288 |
foreach (var input in sampler.Inputs) |
289 |
{ |
290 |
var source = input.Source; |
291 |
EnsureId(source); |
292 |
|
293 |
string sourceId = IdOf(source) + (input.Semantic == Semantic.Output ? valueName : ""); |
294 |
|
295 |
if (!sources.TryGetValue(sourceId, out source)) |
296 |
{ |
297 |
source = input.Source; |
298 |
|
299 |
switch (input.Semantic) |
300 |
{ |
301 |
case Semantic.Input: |
302 |
source = new AnimationSource(source.FloatData, new[] { "TIME" }); |
303 |
break; |
304 |
case Semantic.Output: |
305 |
source = new AnimationSource(source.FloatData, new[] { valueName }); |
306 |
break; |
307 |
case Semantic.Interpolation: |
308 |
source = new AnimationSource(source.NameData, new[] { "INTERPOLATION" }); |
309 |
break; |
310 |
case Semantic.OutTangent: |
311 |
case Semantic.InTangent: |
312 |
source = new AnimationSource(source.FloatData, new[] { "X", "Y" }); |
313 |
break; |
314 |
default: |
315 |
throw new NotSupportedException(string.Format("Invalid semantic {0} for animation input", input.Semantic)); |
316 |
} |
317 |
|
318 |
sources.Add(sourceId, source); |
319 |
|
320 |
EnsureId(source, string.Format(CultureInfo.InvariantCulture, "{0}_{1}", IdOf(valueSampler), input.Semantic.ToString().ToLowerInvariant())); |
321 |
animation.Sources.Add(source); |
322 |
} |
323 |
|
324 |
valueSampler.Inputs.Add(new Input(input.Semantic, source)); |
325 |
} |
326 |
} |
327 |
|
328 |
animation.Channels.Add(new AnimationChannel( |
329 |
valueSampler, |
330 |
string.Format(CultureInfo.InvariantCulture, "{0}/{1}.{2}", IdOf(node), transform.Sid, valueName))); |
331 |
} |
332 |
} |
333 |
|
334 |
#endregion |
335 |
|
336 |
public static void WriteFile(string filePath, Scene scene) |
337 |
{ |
338 |
var writer = new DaeWriter(); |
339 |
writer.visitor = new WriteVisitor(); |
340 |
writer.visitor.VisitScene(scene); |
341 |
writer.mainScene = scene; |
342 |
|
343 |
var settings = new XmlWriterSettings { |
344 |
CloseOutput = true, |
345 |
ConformanceLevel = ConformanceLevel.Document, |
346 |
Encoding = Encoding.UTF8, |
347 |
Indent = true, |
348 |
IndentChars = "\t" |
349 |
}; |
350 |
|
351 |
//AxisConverter.Convert(scene, Axis.Y, Axis.Z); |
352 |
|
353 |
using (var stream = File.Create(filePath)) |
354 |
using (writer.xml = XmlWriter.Create(stream, settings)) |
355 |
writer.WriteRoot(); |
356 |
} |
357 |
|
358 |
private void WriteRoot() |
359 |
{ |
360 |
WriteCollada(); |
361 |
|
362 |
WriteLibrary("library_cameras", visitor.Cameras, WriteCamera); |
363 |
WriteLibrary("library_images", visitor.Images, WriteImage); |
364 |
WriteLibrary("library_effects", visitor.Effects, WriteEffect); |
365 |
WriteLibrary("library_materials", visitor.Materials, WriteMaterial); |
366 |
WriteLibrary("library_geometries", visitor.Geometries, WriteGeometry); |
367 |
WriteLibrary("library_visual_scenes", visitor.Scenes, WriteScene); |
368 |
WriteLibrary("library_animations", visitor.Animations, WriteAnimation); |
369 |
|
370 |
WriteScene(); |
371 |
} |
372 |
|
373 |
private void WriteCollada() |
374 |
{ |
375 |
xml.WriteStartDocument(); |
376 |
xml.WriteStartElement("COLLADA", "http://www.collada.org/2005/11/COLLADASchema"); |
377 |
xml.WriteAttributeString("version", "1.4.0"); |
378 |
|
379 |
xml.WriteStartElement("asset"); |
380 |
xml.WriteStartElement("contributor"); |
381 |
//xml.WriteElementString("author", "OniSplit"); |
382 |
xml.WriteElementString("authoring_tool", string.Format(CultureInfo.InvariantCulture, "OniSplit v{0}", Utils.Version)); |
383 |
xml.WriteEndElement(); |
384 |
|
385 |
xml.WriteStartElement("unit"); |
386 |
xml.WriteAttributeString("meter", "0.1"); |
387 |
xml.WriteAttributeString("name", "decimeter"); |
388 |
xml.WriteEndElement(); |
389 |
xml.WriteElementString("up_axis", "Y_UP"); |
390 |
xml.WriteEndElement(); |
391 |
} |
392 |
|
393 |
private void WriteLibrary<T>(string name, ICollection<T> library, Action<T> entityWriter) |
394 |
{ |
395 |
if (library.Count == 0) |
396 |
return; |
397 |
|
398 |
xml.WriteStartElement(name); |
399 |
|
400 |
foreach (T entity in library) |
401 |
entityWriter(entity); |
402 |
|
403 |
xml.WriteEndElement(); |
404 |
} |
405 |
|
406 |
private void WriteScene() |
407 |
{ |
408 |
xml.WriteStartElement("scene"); |
409 |
xml.WriteStartElement("instance_visual_scene"); |
410 |
xml.WriteAttributeString("url", visitor.UrlOf(mainScene)); |
411 |
xml.WriteEndElement(); |
412 |
xml.WriteEndElement(); |
413 |
} |
414 |
|
415 |
private void WriteImage(Image image) |
416 |
{ |
417 |
BeginEntity("image", image); |
418 |
|
419 |
string imageUrl; |
420 |
|
421 |
if (Path.IsPathRooted(image.FilePath)) |
422 |
imageUrl = "file:///" + image.FilePath.Replace('\\', '/'); |
423 |
else |
424 |
imageUrl = image.FilePath.Replace('\\', '/'); |
425 |
|
426 |
xml.WriteElementString("init_from", imageUrl); |
427 |
|
428 |
EndEntity(); |
429 |
} |
430 |
|
431 |
private void WriteEffect(Effect effect) |
432 |
{ |
433 |
BeginEntity("effect", effect); |
434 |
WriteEffectCommonProfile(effect); |
435 |
EndEntity(); |
436 |
} |
437 |
|
438 |
private void WriteEffectCommonProfile(Effect effect) |
439 |
{ |
440 |
xml.WriteStartElement("profile_COMMON"); |
441 |
|
442 |
foreach (var parameter in effect.Parameters) |
443 |
WriteEffectParameter(parameter); |
444 |
|
445 |
WriteEffectTechnique(effect); |
446 |
|
447 |
xml.WriteEndElement(); |
448 |
} |
449 |
|
450 |
private void WriteEffectParameter(EffectParameter parameter) |
451 |
{ |
452 |
xml.WriteStartElement("newparam"); |
453 |
xml.WriteAttributeString("sid", parameter.Sid); |
454 |
|
455 |
if (!string.IsNullOrEmpty(parameter.Semantic)) |
456 |
{ |
457 |
xml.WriteStartElement("semantic"); |
458 |
xml.WriteString(parameter.Semantic); |
459 |
xml.WriteEndElement(); |
460 |
} |
461 |
|
462 |
if (parameter.Value is float) |
463 |
{ |
464 |
float value = (float)parameter.Value; |
465 |
xml.WriteElementString("float", |
466 |
XmlConvert.ToString(value)); |
467 |
} |
468 |
else if (parameter.Value is Vector2) |
469 |
{ |
470 |
var value = (Vector2)parameter.Value; |
471 |
xml.WriteElementString("float2", string.Format("{0} {1}", |
472 |
XmlConvert.ToString(value.X), XmlConvert.ToString(value.Y))); |
473 |
} |
474 |
else if (parameter.Value is Vector3) |
475 |
{ |
476 |
var value = (Vector3)parameter.Value; |
477 |
xml.WriteElementString("float3", string.Format("{0} {1} {3}", |
478 |
XmlConvert.ToString(value.X), XmlConvert.ToString(value.Y), XmlConvert.ToString(value.Z))); |
479 |
} |
480 |
else if (parameter.Value is EffectSurface) |
481 |
{ |
482 |
var surface = (EffectSurface)parameter.Value; |
483 |
xml.WriteStartElement("surface"); |
484 |
xml.WriteAttributeString("type", "2D"); |
485 |
xml.WriteElementString("init_from", visitor.IdOf(surface.InitFrom)); |
486 |
xml.WriteEndElement(); |
487 |
} |
488 |
else if (parameter.Value is EffectSampler) |
489 |
{ |
490 |
var sampler = (EffectSampler)parameter.Value; |
491 |
xml.WriteStartElement("sampler2D"); |
492 |
xml.WriteStartElement("source"); |
493 |
xml.WriteString(sampler.Surface.DeclaringParameter.Sid); |
494 |
xml.WriteEndElement(); |
495 |
|
496 |
if (sampler.MinFilter != EffectSamplerFilter.None) |
497 |
xml.WriteElementString("minfilter", sampler.MinFilter.ToString().ToUpperInvariant()); |
498 |
|
499 |
if (sampler.MagFilter != EffectSamplerFilter.None) |
500 |
xml.WriteElementString("magfilter", sampler.MagFilter.ToString().ToUpperInvariant()); |
501 |
|
502 |
if (sampler.MipFilter != EffectSamplerFilter.None) |
503 |
xml.WriteElementString("mipfilter", sampler.MipFilter.ToString().ToUpperInvariant()); |
504 |
|
505 |
xml.WriteEndElement(); |
506 |
} |
507 |
|
508 |
xml.WriteEndElement(); |
509 |
} |
510 |
|
511 |
private void WriteEffectTechnique(Effect effect) |
512 |
{ |
513 |
xml.WriteStartElement("technique"); |
514 |
xml.WriteStartElement("phong"); |
515 |
|
516 |
//WriteEffectTechniqueProperty("emission", effect.Emission); |
517 |
|
518 |
WriteEffectTechniqueProperty("ambient", effect.Ambient); |
519 |
WriteEffectTechniqueProperty("diffuse", effect.Diffuse); |
520 |
WriteEffectTechniqueProperty("specular", effect.Specular); |
521 |
|
522 |
//WriteEffectTechniqueProperty("shininess", effect.Shininess); |
523 |
//WriteEffectTechniqueProperty("reflective", effect.Reflective); |
524 |
//WriteEffectTechniqueProperty("reflectivity", effect.Reflectivity); |
525 |
|
526 |
WriteEffectTechniqueProperty("transparent", effect.Transparent); |
527 |
|
528 |
//WriteEffectTechniqueProperty("transparency", effect.Transparency); |
529 |
//WriteEffectTechniqueProperty("index_of_refraction", effect.IndexOfRefraction); |
530 |
|
531 |
xml.WriteEndElement(); |
532 |
xml.WriteEndElement(); |
533 |
} |
534 |
|
535 |
private void WriteEffectTechniqueProperty(string name, EffectParameter value) |
536 |
{ |
537 |
bool isTransparent = name == "transparent"; |
538 |
|
539 |
if (isTransparent && value.Value == null) |
540 |
return; |
541 |
|
542 |
xml.WriteStartElement(name); |
543 |
|
544 |
if (isTransparent) |
545 |
xml.WriteAttributeString("opaque", "A_ONE"); |
546 |
|
547 |
if (value.Reference != null) |
548 |
{ |
549 |
xml.WriteStartElement("param"); |
550 |
xml.WriteString(value.Reference); |
551 |
xml.WriteEndElement(); |
552 |
} |
553 |
else if (value.Value is float) |
554 |
{ |
555 |
float flt = (float)value.Value; |
556 |
xml.WriteStartElement("float"); |
557 |
xml.WriteAttributeString("sid", value.Sid); |
558 |
xml.WriteString(XmlConvert.ToString(flt)); |
559 |
xml.WriteEndElement(); |
560 |
} |
561 |
else if (value.Value is Vector4) |
562 |
{ |
563 |
var color = (Vector4)value.Value; |
564 |
xml.WriteStartElement("color"); |
565 |
xml.WriteAttributeString("sid", value.Sid); |
566 |
xml.WriteString(string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", |
567 |
XmlConvert.ToString(color.X), XmlConvert.ToString(color.Y), XmlConvert.ToString(color.Z), XmlConvert.ToString(color.W))); |
568 |
xml.WriteEndElement(); |
569 |
} |
570 |
else if (value.Value is EffectTexture) |
571 |
{ |
572 |
var texture = (EffectTexture)value.Value; |
573 |
xml.WriteStartElement("texture"); |
574 |
xml.WriteAttributeString("texture", texture.Sampler.Owner.Sid); |
575 |
xml.WriteAttributeString("texcoord", texture.TexCoordSemantic); |
576 |
xml.WriteEndElement(); |
577 |
} |
578 |
|
579 |
xml.WriteEndElement(); |
580 |
} |
581 |
|
582 |
private void WriteMaterial(Material matrial) |
583 |
{ |
584 |
BeginEntity("material", matrial); |
585 |
|
586 |
xml.WriteStartElement("instance_effect"); |
587 |
xml.WriteAttributeString("url", visitor.UrlOf(matrial.Effect)); |
588 |
xml.WriteEndElement(); |
589 |
|
590 |
EndEntity(); |
591 |
} |
592 |
|
593 |
private void WriteGeometry(Geometry geometry) |
594 |
{ |
595 |
BeginEntity("geometry", geometry); |
596 |
|
597 |
xml.WriteStartElement("mesh"); |
598 |
|
599 |
WriteGeometrySources(geometry); |
600 |
WriteGeometryVertices(geometry); |
601 |
|
602 |
foreach (var primitives in geometry.Primitives) |
603 |
WriteGeometryPrimitives(geometry, primitives); |
604 |
|
605 |
xml.WriteEndElement(); |
606 |
|
607 |
EndEntity(); |
608 |
} |
609 |
|
610 |
private void WriteGeometrySources(Geometry geometry) |
611 |
{ |
612 |
var sources = new Dictionary<Source, List<Semantic>>(); |
613 |
|
614 |
foreach (var primitives in geometry.Primitives) |
615 |
{ |
616 |
foreach (var input in primitives.Inputs) |
617 |
{ |
618 |
List<Semantic> uses; |
619 |
|
620 |
if (!sources.TryGetValue(input.Source, out uses)) |
621 |
{ |
622 |
uses = new List<Semantic>(); |
623 |
sources.Add(input.Source, uses); |
624 |
} |
625 |
|
626 |
if (!uses.Contains(input.Semantic)) |
627 |
uses.Add(input.Semantic); |
628 |
} |
629 |
} |
630 |
|
631 |
foreach (var pair in sources) |
632 |
{ |
633 |
foreach (var semantic in pair.Value) |
634 |
WriteSource(pair.Key, semantic); |
635 |
} |
636 |
} |
637 |
|
638 |
private void WriteGeometryVertices(Geometry geometry) |
639 |
{ |
640 |
string baseId = visitor.IdOf(geometry); |
641 |
|
642 |
if (baseId.EndsWith("_geometry", StringComparison.Ordinal)) |
643 |
baseId = baseId.Substring(0, baseId.Length - "_geometry".Length); |
644 |
|
645 |
xml.WriteStartElement("vertices"); |
646 |
xml.WriteAttributeString("id", baseId + "_vertices"); |
647 |
|
648 |
foreach (var input in geometry.Vertices) |
649 |
{ |
650 |
xml.WriteStartElement("input"); |
651 |
WriteSemanticAttribute("semantic", input.Semantic); |
652 |
xml.WriteAttributeString("source", visitor.UrlOf(input.Source)); |
653 |
xml.WriteEndElement(); |
654 |
} |
655 |
|
656 |
xml.WriteEndElement(); |
657 |
} |
658 |
|
659 |
private void WriteGeometryPrimitives(Geometry geometry, MeshPrimitives primitives) |
660 |
{ |
661 |
switch (primitives.PrimitiveType) |
662 |
{ |
663 |
case MeshPrimitiveType.Lines: |
664 |
case MeshPrimitiveType.LineStrips: |
665 |
case MeshPrimitiveType.TriangleFans: |
666 |
case MeshPrimitiveType.TriangleStrips: |
667 |
throw new NotSupportedException(string.Format("Writing {0} is not supported", primitives.PrimitiveType)); |
668 |
} |
669 |
|
670 |
bool trianglesOnly = !primitives.VertexCounts.Exists(x => x != 3); |
671 |
|
672 |
if (!trianglesOnly) |
673 |
xml.WriteStartElement("polylist"); |
674 |
else |
675 |
xml.WriteStartElement("triangles"); |
676 |
|
677 |
xml.WriteAttributeString("count", XmlConvert.ToString(primitives.VertexCounts.Count)); |
678 |
|
679 |
if (!string.IsNullOrEmpty(primitives.MaterialSymbol)) |
680 |
xml.WriteAttributeString("material", primitives.MaterialSymbol); |
681 |
|
682 |
int offset = 0; |
683 |
bool vertexInputWritten = false; |
684 |
|
685 |
var inputs = new List<IndexedInput>(); |
686 |
|
687 |
string baseUrl = visitor.UrlOf(geometry); |
688 |
|
689 |
if (baseUrl.EndsWith("_geometry", StringComparison.Ordinal)) |
690 |
baseUrl = baseUrl.Substring(0, baseUrl.Length - "_geometry".Length); |
691 |
|
692 |
foreach (var input in primitives.Inputs) |
693 |
{ |
694 |
if (geometry.Vertices.Any(x => x.Source == input.Source)) |
695 |
{ |
696 |
if (!vertexInputWritten) |
697 |
{ |
698 |
inputs.Add(input); |
699 |
|
700 |
xml.WriteStartElement("input"); |
701 |
xml.WriteAttributeString("semantic", "VERTEX"); |
702 |
xml.WriteAttributeString("source", baseUrl + "_vertices"); |
703 |
xml.WriteAttributeString("offset", XmlConvert.ToString(offset++)); |
704 |
xml.WriteEndElement(); |
705 |
} |
706 |
|
707 |
vertexInputWritten = true; |
708 |
} |
709 |
else |
710 |
{ |
711 |
inputs.Add(input); |
712 |
|
713 |
xml.WriteStartElement("input"); |
714 |
WriteSemanticAttribute("semantic", input.Semantic); |
715 |
xml.WriteAttributeString("source", visitor.UrlOf(input.Source)); |
716 |
xml.WriteAttributeString("offset", XmlConvert.ToString(offset++)); |
717 |
|
718 |
if (input.Set != 0) |
719 |
xml.WriteAttributeString("set", XmlConvert.ToString(input.Set)); |
720 |
|
721 |
xml.WriteEndElement(); |
722 |
} |
723 |
} |
724 |
|
725 |
if (!trianglesOnly) |
726 |
{ |
727 |
xml.WriteStartElement("vcount"); |
728 |
xml.WriteWhitespace("\n"); |
729 |
|
730 |
int vertexCount = 0; |
731 |
int c = 0; |
732 |
|
733 |
foreach (int i in primitives.VertexCounts) |
734 |
{ |
735 |
xml.WriteString(XmlConvert.ToString(i) + " "); |
736 |
vertexCount += i; |
737 |
|
738 |
c++; |
739 |
|
740 |
if (c == 32) |
741 |
{ |
742 |
xml.WriteWhitespace("\n"); |
743 |
c = 0; |
744 |
} |
745 |
} |
746 |
|
747 |
xml.WriteEndElement(); |
748 |
} |
749 |
|
750 |
xml.WriteStartElement("p"); |
751 |
xml.WriteWhitespace("\n"); |
752 |
|
753 |
int polygonStartIndex = 0; |
754 |
|
755 |
foreach (int vertexCount in primitives.VertexCounts) |
756 |
{ |
757 |
for (int index = 0; index < vertexCount; index++) |
758 |
{ |
759 |
foreach (var input in inputs) |
760 |
{ |
761 |
xml.WriteString(XmlConvert.ToString(input.Indices[polygonStartIndex + index])); |
762 |
|
763 |
if (input != inputs.Last() || index != vertexCount - 1) |
764 |
xml.WriteWhitespace(" "); |
765 |
} |
766 |
} |
767 |
|
768 |
xml.WriteWhitespace("\n"); |
769 |
polygonStartIndex += vertexCount; |
770 |
} |
771 |
|
772 |
xml.WriteEndElement(); |
773 |
|
774 |
xml.WriteEndElement(); |
775 |
} |
776 |
|
777 |
private void WriteScene(Scene scene) |
778 |
{ |
779 |
BeginEntity("visual_scene", scene); |
780 |
|
781 |
foreach (var node in scene.Nodes) |
782 |
WriteSceneNode(node); |
783 |
|
784 |
EndEntity(); |
785 |
} |
786 |
|
787 |
private void WriteSceneNode(Node node) |
788 |
{ |
789 |
BeginEntity("node", node); |
790 |
|
791 |
foreach (var transform in node.Transforms) |
792 |
WriteNodeTransform(transform); |
793 |
|
794 |
foreach (var instance in node.Instances) |
795 |
{ |
796 |
if (instance is GeometryInstance) |
797 |
WriteGeometryInstance((GeometryInstance)instance); |
798 |
else if (instance is CameraInstance) |
799 |
WriteCameraInstance((CameraInstance)instance); |
800 |
} |
801 |
|
802 |
foreach (var child in node.Nodes) |
803 |
WriteSceneNode(child); |
804 |
|
805 |
EndEntity(); |
806 |
} |
807 |
|
808 |
private void WriteNodeTransform(Transform transform) |
809 |
{ |
810 |
string type; |
811 |
|
812 |
if (transform is TransformTranslate) |
813 |
type = "translate"; |
814 |
else if (transform is TransformRotate) |
815 |
type = "rotate"; |
816 |
else if (transform is TransformScale) |
817 |
type = "scale"; |
818 |
else |
819 |
type = "matrix"; |
820 |
|
821 |
xml.WriteStartElement(type); |
822 |
|
823 |
if (!string.IsNullOrEmpty(transform.Sid)) |
824 |
xml.WriteAttributeString("sid", transform.Sid); |
825 |
|
826 |
var values = new StringBuilder(transform.Values.Length * 16); |
827 |
|
828 |
foreach (float value in transform.Values) |
829 |
values.AppendFormat(CultureInfo.InvariantCulture, "{0:f6} ", value); |
830 |
|
831 |
if (values.Length > 0) |
832 |
values.Length--; |
833 |
|
834 |
xml.WriteValue(values.ToString()); |
835 |
|
836 |
xml.WriteEndElement(); |
837 |
} |
838 |
|
839 |
private void WriteCameraInstance(CameraInstance instance) |
840 |
{ |
841 |
xml.WriteStartElement("instance_camera"); |
842 |
xml.WriteAttributeString("url", visitor.UrlOf(instance.Target)); |
843 |
xml.WriteEndElement(); |
844 |
} |
845 |
|
846 |
private void WriteGeometryInstance(GeometryInstance instance) |
847 |
{ |
848 |
xml.WriteStartElement("instance_geometry"); |
849 |
xml.WriteAttributeString("url", visitor.UrlOf(instance.Target)); |
850 |
|
851 |
if (instance.Materials.Count > 0) |
852 |
{ |
853 |
xml.WriteStartElement("bind_material"); |
854 |
xml.WriteStartElement("technique_common"); |
855 |
|
856 |
foreach (var matInstance in instance.Materials) |
857 |
WriteMaterialInstance(matInstance); |
858 |
|
859 |
xml.WriteEndElement(); |
860 |
xml.WriteEndElement(); |
861 |
} |
862 |
|
863 |
xml.WriteEndElement(); |
864 |
} |
865 |
|
866 |
private void WriteMaterialInstance(MaterialInstance matInstance) |
867 |
{ |
868 |
xml.WriteStartElement("instance_material"); |
869 |
xml.WriteAttributeString("symbol", matInstance.Symbol); |
870 |
xml.WriteAttributeString("target", visitor.UrlOf(matInstance.Target)); |
871 |
|
872 |
foreach (var binding in matInstance.Bindings) |
873 |
{ |
874 |
xml.WriteStartElement("bind_vertex_input"); |
875 |
xml.WriteAttributeString("semantic", binding.Semantic); |
876 |
WriteSemanticAttribute("input_semantic", binding.VertexInput.Semantic); |
877 |
xml.WriteAttributeString("input_set", XmlConvert.ToString(binding.VertexInput.Set)); |
878 |
xml.WriteEndElement(); |
879 |
} |
880 |
|
881 |
xml.WriteEndElement(); |
882 |
} |
883 |
|
884 |
private void WriteAnimation(Animation animation) |
885 |
{ |
886 |
BeginEntity("animation", animation); |
887 |
|
888 |
foreach (var source in animation.Sources) |
889 |
WriteSource(source, Semantic.None); |
890 |
|
891 |
foreach (var sampler in animation.Samplers) |
892 |
WriteAnimationSampler(animation, sampler); |
893 |
|
894 |
foreach (var channel in animation.Channels) |
895 |
WriteAnimationChannel(channel); |
896 |
|
897 |
EndEntity(); |
898 |
} |
899 |
|
900 |
private void WriteAnimationSampler(Animation animation, Sampler sampler) |
901 |
{ |
902 |
xml.WriteStartElement("sampler"); |
903 |
xml.WriteAttributeString("id", visitor.IdOf(sampler)); |
904 |
|
905 |
foreach (var input in sampler.Inputs) |
906 |
{ |
907 |
xml.WriteStartElement("input"); |
908 |
WriteSemanticAttribute("semantic", input.Semantic); |
909 |
xml.WriteAttributeString("source", visitor.UrlOf(input.Source)); |
910 |
xml.WriteEndElement(); |
911 |
} |
912 |
|
913 |
xml.WriteEndElement(); |
914 |
} |
915 |
|
916 |
private void WriteAnimationChannel(AnimationChannel channel) |
917 |
{ |
918 |
xml.WriteStartElement("channel"); |
919 |
xml.WriteAttributeString("source", visitor.UrlOf(channel.Sampler)); |
920 |
xml.WriteAttributeString("target", channel.TargetPath); |
921 |
xml.WriteEndElement(); |
922 |
} |
923 |
|
924 |
private void BeginEntity(string name, Entity entity) |
925 |
{ |
926 |
xml.WriteStartElement(name); |
927 |
|
928 |
string id = visitor.IdOf(entity); |
929 |
|
930 |
if (!string.IsNullOrEmpty(id)) |
931 |
xml.WriteAttributeString("id", id); |
932 |
|
933 |
//if (!String.IsNullOrEmpty(entity.Name)) |
934 |
// xml.WriteAttributeString("name", entity.Name); |
935 |
} |
936 |
|
937 |
private void EndEntity() |
938 |
{ |
939 |
xml.WriteEndElement(); |
940 |
} |
941 |
|
942 |
private void WriteSource(Source source, Semantic semantic) |
943 |
{ |
944 |
if (writtenSources.ContainsKey(source)) |
945 |
return; |
946 |
|
947 |
string sourceId = visitor.IdOf(source); |
948 |
|
949 |
writtenSources.Add(source, sourceId); |
950 |
|
951 |
var animationSource = source as AnimationSource; |
952 |
|
953 |
if (animationSource != null) |
954 |
{ |
955 |
if (source.FloatData != null) |
956 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, animationSource.Parameters); |
957 |
else |
958 |
WriteSource(sourceId, source.NameData, x => x, source.Stride, animationSource.Parameters); |
959 |
|
960 |
return; |
961 |
} |
962 |
|
963 |
switch (semantic) |
964 |
{ |
965 |
case Semantic.Position: |
966 |
case Semantic.Normal: |
967 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "X", "Y", "Z" }); |
968 |
break; |
969 |
|
970 |
case Semantic.TexCoord: |
971 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "S", "T" }); |
972 |
break; |
973 |
|
974 |
case Semantic.Color: |
975 |
if (source.Stride == 4) |
976 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "R", "G", "B", "A" }); |
977 |
else |
978 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "R", "G", "B" }); |
979 |
break; |
980 |
|
981 |
case Semantic.Input: |
982 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "TIME" }); |
983 |
return; |
984 |
|
985 |
case Semantic.Output: |
986 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "VALUE" }); |
987 |
return; |
988 |
|
989 |
case Semantic.Interpolation: |
990 |
WriteSource(sourceId, source.NameData, x => x, source.Stride, new[] { "INTERPOLATION" }); |
991 |
return; |
992 |
|
993 |
case Semantic.InTangent: |
994 |
case Semantic.OutTangent: |
995 |
WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "X", "Y" }); |
996 |
break; |
997 |
|
998 |
default: |
999 |
throw new NotSupportedException(string.Format("Sources with semantic {0} are not supported", semantic)); |
1000 |
} |
1001 |
} |
1002 |
|
1003 |
private void WriteSource<T>(string sourceId, T[] data, Func<T, string> toString, int stride, string[] paramNames) |
1004 |
{ |
1005 |
string arrayId = sourceId + "_array"; |
1006 |
string type = null; |
1007 |
|
1008 |
if (typeof(T) == typeof(float)) |
1009 |
type = "float"; |
1010 |
else if (typeof(T) == typeof(string)) |
1011 |
type = "Name"; |
1012 |
|
1013 |
xml.WriteStartElement("source"); |
1014 |
xml.WriteAttributeString("id", sourceId); |
1015 |
|
1016 |
xml.WriteStartElement(type + "_array"); |
1017 |
xml.WriteAttributeString("id", arrayId); |
1018 |
xml.WriteAttributeString("count", XmlConvert.ToString(data.Length)); |
1019 |
xml.WriteWhitespace("\n"); |
1020 |
|
1021 |
int valuesPerLine = (stride == 1) ? 10 : stride; |
1022 |
|
1023 |
for (int i = 0; i < data.Length; i++) |
1024 |
{ |
1025 |
xml.WriteString(toString(data[i])); |
1026 |
|
1027 |
if (i != data.Length - 1) |
1028 |
{ |
1029 |
if (i % valuesPerLine == valuesPerLine - 1) |
1030 |
xml.WriteWhitespace("\n"); |
1031 |
else |
1032 |
xml.WriteWhitespace(" "); |
1033 |
} |
1034 |
} |
1035 |
|
1036 |
xml.WriteEndElement(); |
1037 |
|
1038 |
xml.WriteStartElement("technique_common"); |
1039 |
WriteSourceAccessor<T>(arrayId, data.Length / stride, stride, type, paramNames); |
1040 |
xml.WriteEndElement(); |
1041 |
|
1042 |
xml.WriteEndElement(); |
1043 |
} |
1044 |
|
1045 |
private void WriteSourceAccessor<T>(string arrayId, int count, int stride, string type, string[] paramNames) |
1046 |
{ |
1047 |
xml.WriteStartElement("accessor"); |
1048 |
xml.WriteAttributeString("source", "#" + arrayId); |
1049 |
xml.WriteAttributeString("count", XmlConvert.ToString(count)); |
1050 |
xml.WriteAttributeString("stride", XmlConvert.ToString(stride)); |
1051 |
|
1052 |
for (int i = 0; i < stride; i++) |
1053 |
{ |
1054 |
xml.WriteStartElement("param"); |
1055 |
xml.WriteAttributeString("type", type); |
1056 |
xml.WriteAttributeString("name", paramNames[i]); |
1057 |
xml.WriteEndElement(); |
1058 |
} |
1059 |
|
1060 |
xml.WriteEndElement(); |
1061 |
} |
1062 |
|
1063 |
private void WriteSemanticAttribute(string name, Semantic semantic) |
1064 |
{ |
1065 |
xml.WriteAttributeString(name, semantic.ToString().ToUpperInvariant()); |
1066 |
} |
1067 |
|
1068 |
private void WriteCamera(Camera camera) |
1069 |
{ |
1070 |
BeginEntity("camera", camera); |
1071 |
|
1072 |
xml.WriteStartElement("optics"); |
1073 |
xml.WriteStartElement("technique_common"); |
1074 |
xml.WriteStartElement("perspective"); |
1075 |
xml.WriteElementString("xfov", XmlConvert.ToString(camera.XFov)); |
1076 |
xml.WriteElementString("aspect_ratio", XmlConvert.ToString(camera.AspectRatio)); |
1077 |
xml.WriteElementString("znear", XmlConvert.ToString(camera.ZNear)); |
1078 |
xml.WriteElementString("zfar", XmlConvert.ToString(camera.ZFar)); |
1079 |
xml.WriteEndElement(); |
1080 |
xml.WriteEndElement(); |
1081 |
xml.WriteEndElement(); |
1082 |
|
1083 |
EndEntity(); |
1084 |
} |
1085 |
} |
1086 |
} |