using System; using System.Collections.Generic; using System.IO; using System.Globalization; using System.Text; using System.Xml; namespace Oni.Dae.IO { internal class DaeWriter { #region Private data private XmlWriter xml; private Scene mainScene; private WriteVisitor visitor; private Dictionary writtenSources = new Dictionary(); #endregion #region private class Animation private class Animation : Entity { public readonly List Sources = new List(); public readonly List Samplers = new List(); public readonly List Channels = new List(); } #endregion #region private class AnimationChannel private class AnimationChannel { public readonly Sampler Sampler; public readonly string TargetPath; public AnimationChannel(Sampler sampler, string targetPath) { this.Sampler = sampler; this.TargetPath = targetPath; } } #endregion #region private class AnimationSource private class AnimationSource : Source { public readonly string[] Parameters; public AnimationSource(float[] data, string[] parameters) : base(data, parameters.Length) { this.Parameters = parameters; } public AnimationSource(string[] data, string[] parameters) : base(data, parameters.Length) { this.Parameters = parameters; } } #endregion #region private class WriteVisitor private class WriteVisitor : Visitor { private readonly Dictionary entities = new Dictionary(); private readonly Dictionary ids = new Dictionary(StringComparer.Ordinal); private readonly Dictionary samplers = new Dictionary(StringComparer.Ordinal); private readonly Dictionary sources = new Dictionary(StringComparer.Ordinal); private int uniqueEntityId = 1; public readonly List Images = new List(); public readonly List Effects = new List(); public readonly List Materials = new List(); public readonly List Geometries = new List(); public readonly List Scenes = new List(); public readonly List Animations = new List(); public readonly List Cameras = new List(); public override void VisitScene(Scene scene) { AddEntity(scene); base.VisitScene(scene); } public override void VisitNode(Node node) { EnsureId(node); foreach (var transform in node.Transforms.Where(t => t.HasAnimations)) { for (int i = 0; i < transform.Animations.Length; i++) { var sampler = transform.Animations[i]; if (sampler != null) AddAnimationChannel(sampler, node, transform, i); } } base.VisitNode(node); } public override void VisitGeometry(Geometry geometry) { AddEntity(geometry); string baseId = IdOf(geometry); if (baseId.EndsWith("_geometry", StringComparison.Ordinal)) baseId = baseId.Substring(0, baseId.Length - "_geometry".Length); foreach (var input in geometry.Vertices) EnsureId(input.Source, string.Format(CultureInfo.InvariantCulture, "{0}_{1}", baseId, input.Semantic.ToString().ToLowerInvariant())); foreach (var input in geometry.Primitives.SelectMany(p => p.Inputs)) EnsureId(input.Source, string.Format(CultureInfo.InvariantCulture, "{0}_{1}", baseId, input.Semantic.ToString().ToLowerInvariant())); base.VisitGeometry(geometry); } public override void VisitMaterial(Material material) { AddEntity(material); base.VisitMaterial(material); } public override void VisitEffect(Effect effect) { AddEntity(effect); base.VisitEffect(effect); } public override void VisitImage(Image image) { AddEntity(image); base.VisitImage(image); } public override void VisitCamera(Camera camera) { AddEntity(camera); base.VisitCamera(camera); } private void AddEntity(Scene scene) { AddEntity(scene, Scenes); } private void AddEntity(Image image) { AddEntity(image, Images); } private void AddEntity(Effect effect) { AddEntity(effect, Effects); } private void AddEntity(Material material) { AddEntity(material, Materials); } private void AddEntity(Geometry geometry) { AddEntity(geometry, Geometries); } private void AddEntity(Animation animation) { AddEntity(animation, Animations); } private void AddEntity(Camera camera) { AddEntity(camera, Cameras); } private void AddEntity(T entity, ICollection entityCollection) where T : Entity { if (EnsureId(entity)) entityCollection.Add(entity); } private bool EnsureId(Entity entity) { if (entities.ContainsKey(entity)) return false; string name = entity.Name; string id; if (string.IsNullOrEmpty(name)) { do { id = string.Format(CultureInfo.InvariantCulture, "unique_{0}", uniqueEntityId++, entity.GetType().Name.ToLowerInvariant()); } while (ids.ContainsKey(id)); } else { if (!ids.ContainsKey(name)) { id = name; } else { id = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", name, entity.GetType().Name.ToLowerInvariant()); while (ids.ContainsKey(id)) { id = string.Format(CultureInfo.InvariantCulture, "{0}_{1}_{2}", name, uniqueEntityId++, entity.GetType().Name.ToLowerInvariant()); } } } entities.Add(entity, id); ids.Add(id, entity); return true; } private bool EnsureId(Entity entity, string id) { if (entities.ContainsKey(entity)) return false; entities.Add(entity, id); ids.Add(id, entity); return true; } public string IdOf(Entity entity) { string id; entities.TryGetValue(entity, out id); return id; } public string UrlOf(Entity entity) { return string.Format("#{0}", IdOf(entity)); } private void AddAnimationChannel(Sampler sampler, Node node, Transform transform, int valueIndex) { Animation animation; if (Animations.Count == 0) { animation = new Animation(); Animations.Add(animation); } else { animation = Animations[0]; } EnsureId(sampler); string nodeId = IdOf(node); string samplerId = IdOf(sampler); string valueName = transform.ValueIndexToValueName(valueIndex); Sampler valueSampler; if (!samplers.TryGetValue(samplerId + valueName, out valueSampler)) { valueSampler = new Sampler(); EnsureId(valueSampler, string.Format("{0}_{1}_{2}", IdOf(node), transform.Sid, valueName)); animation.Samplers.Add(valueSampler); foreach (var input in sampler.Inputs) { var source = input.Source; EnsureId(source); string sourceId = IdOf(source) + (input.Semantic == Semantic.Output ? valueName : ""); if (!sources.TryGetValue(sourceId, out source)) { source = input.Source; switch (input.Semantic) { case Semantic.Input: source = new AnimationSource(source.FloatData, new[] { "TIME" }); break; case Semantic.Output: source = new AnimationSource(source.FloatData, new[] { valueName }); break; case Semantic.Interpolation: source = new AnimationSource(source.NameData, new[] { "INTERPOLATION" }); break; case Semantic.OutTangent: case Semantic.InTangent: source = new AnimationSource(source.FloatData, new[] { "X", "Y" }); break; default: throw new NotSupportedException(string.Format("Invalid semantic {0} for animation input", input.Semantic)); } sources.Add(sourceId, source); EnsureId(source, string.Format(CultureInfo.InvariantCulture, "{0}_{1}", IdOf(valueSampler), input.Semantic.ToString().ToLowerInvariant())); animation.Sources.Add(source); } valueSampler.Inputs.Add(new Input(input.Semantic, source)); } } animation.Channels.Add(new AnimationChannel( valueSampler, string.Format(CultureInfo.InvariantCulture, "{0}/{1}.{2}", IdOf(node), transform.Sid, valueName))); } } #endregion public static void WriteFile(string filePath, Scene scene) { var writer = new DaeWriter(); writer.visitor = new WriteVisitor(); writer.visitor.VisitScene(scene); writer.mainScene = scene; var settings = new XmlWriterSettings { CloseOutput = true, ConformanceLevel = ConformanceLevel.Document, Encoding = Encoding.UTF8, Indent = true, IndentChars = "\t" }; //AxisConverter.Convert(scene, Axis.Y, Axis.Z); using (var stream = File.Create(filePath)) using (writer.xml = XmlWriter.Create(stream, settings)) writer.WriteRoot(); } private void WriteRoot() { WriteCollada(); WriteLibrary("library_cameras", visitor.Cameras, WriteCamera); WriteLibrary("library_images", visitor.Images, WriteImage); WriteLibrary("library_effects", visitor.Effects, WriteEffect); WriteLibrary("library_materials", visitor.Materials, WriteMaterial); WriteLibrary("library_geometries", visitor.Geometries, WriteGeometry); WriteLibrary("library_visual_scenes", visitor.Scenes, WriteScene); WriteLibrary("library_animations", visitor.Animations, WriteAnimation); WriteScene(); } private void WriteCollada() { xml.WriteStartDocument(); xml.WriteStartElement("COLLADA", "http://www.collada.org/2005/11/COLLADASchema"); xml.WriteAttributeString("version", "1.4.0"); xml.WriteStartElement("asset"); xml.WriteStartElement("contributor"); //xml.WriteElementString("author", "OniSplit"); xml.WriteElementString("authoring_tool", string.Format(CultureInfo.InvariantCulture, "OniSplit v{0}", Utils.Version)); xml.WriteEndElement(); xml.WriteStartElement("unit"); xml.WriteAttributeString("meter", "0.1"); xml.WriteAttributeString("name", "decimeter"); xml.WriteEndElement(); xml.WriteElementString("up_axis", "Y_UP"); xml.WriteEndElement(); } private void WriteLibrary(string name, ICollection library, Action entityWriter) { if (library.Count == 0) return; xml.WriteStartElement(name); foreach (T entity in library) entityWriter(entity); xml.WriteEndElement(); } private void WriteScene() { xml.WriteStartElement("scene"); xml.WriteStartElement("instance_visual_scene"); xml.WriteAttributeString("url", visitor.UrlOf(mainScene)); xml.WriteEndElement(); xml.WriteEndElement(); } private void WriteImage(Image image) { BeginEntity("image", image); string imageUrl; if (Path.IsPathRooted(image.FilePath)) imageUrl = "file:///" + image.FilePath.Replace('\\', '/'); else imageUrl = image.FilePath.Replace('\\', '/'); xml.WriteElementString("init_from", imageUrl); EndEntity(); } private void WriteEffect(Effect effect) { BeginEntity("effect", effect); WriteEffectCommonProfile(effect); EndEntity(); } private void WriteEffectCommonProfile(Effect effect) { xml.WriteStartElement("profile_COMMON"); foreach (var parameter in effect.Parameters) WriteEffectParameter(parameter); WriteEffectTechnique(effect); xml.WriteEndElement(); } private void WriteEffectParameter(EffectParameter parameter) { xml.WriteStartElement("newparam"); xml.WriteAttributeString("sid", parameter.Sid); if (!string.IsNullOrEmpty(parameter.Semantic)) { xml.WriteStartElement("semantic"); xml.WriteString(parameter.Semantic); xml.WriteEndElement(); } if (parameter.Value is float) { float value = (float)parameter.Value; xml.WriteElementString("float", XmlConvert.ToString(value)); } else if (parameter.Value is Vector2) { var value = (Vector2)parameter.Value; xml.WriteElementString("float2", string.Format("{0} {1}", XmlConvert.ToString(value.X), XmlConvert.ToString(value.Y))); } else if (parameter.Value is Vector3) { var value = (Vector3)parameter.Value; xml.WriteElementString("float3", string.Format("{0} {1} {3}", XmlConvert.ToString(value.X), XmlConvert.ToString(value.Y), XmlConvert.ToString(value.Z))); } else if (parameter.Value is EffectSurface) { var surface = (EffectSurface)parameter.Value; xml.WriteStartElement("surface"); xml.WriteAttributeString("type", "2D"); xml.WriteElementString("init_from", visitor.IdOf(surface.InitFrom)); xml.WriteEndElement(); } else if (parameter.Value is EffectSampler) { var sampler = (EffectSampler)parameter.Value; xml.WriteStartElement("sampler2D"); xml.WriteStartElement("source"); xml.WriteString(sampler.Surface.DeclaringParameter.Sid); xml.WriteEndElement(); if (sampler.MinFilter != EffectSamplerFilter.None) xml.WriteElementString("minfilter", sampler.MinFilter.ToString().ToUpperInvariant()); if (sampler.MagFilter != EffectSamplerFilter.None) xml.WriteElementString("magfilter", sampler.MagFilter.ToString().ToUpperInvariant()); if (sampler.MipFilter != EffectSamplerFilter.None) xml.WriteElementString("mipfilter", sampler.MipFilter.ToString().ToUpperInvariant()); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteEffectTechnique(Effect effect) { xml.WriteStartElement("technique"); xml.WriteStartElement("phong"); //WriteEffectTechniqueProperty("emission", effect.Emission); WriteEffectTechniqueProperty("ambient", effect.Ambient); WriteEffectTechniqueProperty("diffuse", effect.Diffuse); WriteEffectTechniqueProperty("specular", effect.Specular); //WriteEffectTechniqueProperty("shininess", effect.Shininess); //WriteEffectTechniqueProperty("reflective", effect.Reflective); //WriteEffectTechniqueProperty("reflectivity", effect.Reflectivity); WriteEffectTechniqueProperty("transparent", effect.Transparent); //WriteEffectTechniqueProperty("transparency", effect.Transparency); //WriteEffectTechniqueProperty("index_of_refraction", effect.IndexOfRefraction); xml.WriteEndElement(); xml.WriteEndElement(); } private void WriteEffectTechniqueProperty(string name, EffectParameter value) { bool isTransparent = name == "transparent"; if (isTransparent && value.Value == null) return; xml.WriteStartElement(name); if (isTransparent) xml.WriteAttributeString("opaque", "A_ONE"); if (value.Reference != null) { xml.WriteStartElement("param"); xml.WriteString(value.Reference); xml.WriteEndElement(); } else if (value.Value is float) { float flt = (float)value.Value; xml.WriteStartElement("float"); xml.WriteAttributeString("sid", value.Sid); xml.WriteString(XmlConvert.ToString(flt)); xml.WriteEndElement(); } else if (value.Value is Vector4) { var color = (Vector4)value.Value; xml.WriteStartElement("color"); xml.WriteAttributeString("sid", value.Sid); xml.WriteString(string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", XmlConvert.ToString(color.X), XmlConvert.ToString(color.Y), XmlConvert.ToString(color.Z), XmlConvert.ToString(color.W))); xml.WriteEndElement(); } else if (value.Value is EffectTexture) { var texture = (EffectTexture)value.Value; xml.WriteStartElement("texture"); xml.WriteAttributeString("texture", texture.Sampler.Owner.Sid); xml.WriteAttributeString("texcoord", texture.TexCoordSemantic); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteMaterial(Material matrial) { BeginEntity("material", matrial); xml.WriteStartElement("instance_effect"); xml.WriteAttributeString("url", visitor.UrlOf(matrial.Effect)); xml.WriteEndElement(); EndEntity(); } private void WriteGeometry(Geometry geometry) { BeginEntity("geometry", geometry); xml.WriteStartElement("mesh"); WriteGeometrySources(geometry); WriteGeometryVertices(geometry); foreach (var primitives in geometry.Primitives) WriteGeometryPrimitives(geometry, primitives); xml.WriteEndElement(); EndEntity(); } private void WriteGeometrySources(Geometry geometry) { var sources = new Dictionary>(); foreach (var primitives in geometry.Primitives) { foreach (var input in primitives.Inputs) { List uses; if (!sources.TryGetValue(input.Source, out uses)) { uses = new List(); sources.Add(input.Source, uses); } if (!uses.Contains(input.Semantic)) uses.Add(input.Semantic); } } foreach (var pair in sources) { foreach (var semantic in pair.Value) WriteSource(pair.Key, semantic); } } private void WriteGeometryVertices(Geometry geometry) { string baseId = visitor.IdOf(geometry); if (baseId.EndsWith("_geometry", StringComparison.Ordinal)) baseId = baseId.Substring(0, baseId.Length - "_geometry".Length); xml.WriteStartElement("vertices"); xml.WriteAttributeString("id", baseId + "_vertices"); foreach (var input in geometry.Vertices) { xml.WriteStartElement("input"); WriteSemanticAttribute("semantic", input.Semantic); xml.WriteAttributeString("source", visitor.UrlOf(input.Source)); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteGeometryPrimitives(Geometry geometry, MeshPrimitives primitives) { switch (primitives.PrimitiveType) { case MeshPrimitiveType.Lines: case MeshPrimitiveType.LineStrips: case MeshPrimitiveType.TriangleFans: case MeshPrimitiveType.TriangleStrips: throw new NotSupportedException(string.Format("Writing {0} is not supported", primitives.PrimitiveType)); } bool trianglesOnly = !primitives.VertexCounts.Exists(x => x != 3); if (!trianglesOnly) xml.WriteStartElement("polylist"); else xml.WriteStartElement("triangles"); xml.WriteAttributeString("count", XmlConvert.ToString(primitives.VertexCounts.Count)); if (!string.IsNullOrEmpty(primitives.MaterialSymbol)) xml.WriteAttributeString("material", primitives.MaterialSymbol); int offset = 0; bool vertexInputWritten = false; var inputs = new List(); string baseUrl = visitor.UrlOf(geometry); if (baseUrl.EndsWith("_geometry", StringComparison.Ordinal)) baseUrl = baseUrl.Substring(0, baseUrl.Length - "_geometry".Length); foreach (var input in primitives.Inputs) { if (geometry.Vertices.Any(x => x.Source == input.Source)) { if (!vertexInputWritten) { inputs.Add(input); xml.WriteStartElement("input"); xml.WriteAttributeString("semantic", "VERTEX"); xml.WriteAttributeString("source", baseUrl + "_vertices"); xml.WriteAttributeString("offset", XmlConvert.ToString(offset++)); xml.WriteEndElement(); } vertexInputWritten = true; } else { inputs.Add(input); xml.WriteStartElement("input"); WriteSemanticAttribute("semantic", input.Semantic); xml.WriteAttributeString("source", visitor.UrlOf(input.Source)); xml.WriteAttributeString("offset", XmlConvert.ToString(offset++)); if (input.Set != 0) xml.WriteAttributeString("set", XmlConvert.ToString(input.Set)); xml.WriteEndElement(); } } if (!trianglesOnly) { xml.WriteStartElement("vcount"); xml.WriteWhitespace("\n"); int vertexCount = 0; int c = 0; foreach (int i in primitives.VertexCounts) { xml.WriteString(XmlConvert.ToString(i) + " "); vertexCount += i; c++; if (c == 32) { xml.WriteWhitespace("\n"); c = 0; } } xml.WriteEndElement(); } xml.WriteStartElement("p"); xml.WriteWhitespace("\n"); int polygonStartIndex = 0; foreach (int vertexCount in primitives.VertexCounts) { for (int index = 0; index < vertexCount; index++) { foreach (var input in inputs) { xml.WriteString(XmlConvert.ToString(input.Indices[polygonStartIndex + index])); if (input != inputs.Last() || index != vertexCount - 1) xml.WriteWhitespace(" "); } } xml.WriteWhitespace("\n"); polygonStartIndex += vertexCount; } xml.WriteEndElement(); xml.WriteEndElement(); } private void WriteScene(Scene scene) { BeginEntity("visual_scene", scene); foreach (var node in scene.Nodes) WriteSceneNode(node); EndEntity(); } private void WriteSceneNode(Node node) { BeginEntity("node", node); foreach (var transform in node.Transforms) WriteNodeTransform(transform); foreach (var instance in node.Instances) { if (instance is GeometryInstance) WriteGeometryInstance((GeometryInstance)instance); else if (instance is CameraInstance) WriteCameraInstance((CameraInstance)instance); } foreach (var child in node.Nodes) WriteSceneNode(child); EndEntity(); } private void WriteNodeTransform(Transform transform) { string type; if (transform is TransformTranslate) type = "translate"; else if (transform is TransformRotate) type = "rotate"; else if (transform is TransformScale) type = "scale"; else type = "matrix"; xml.WriteStartElement(type); if (!string.IsNullOrEmpty(transform.Sid)) xml.WriteAttributeString("sid", transform.Sid); var values = new StringBuilder(transform.Values.Length * 16); foreach (float value in transform.Values) values.AppendFormat(CultureInfo.InvariantCulture, "{0:f6} ", value); if (values.Length > 0) values.Length--; xml.WriteValue(values.ToString()); xml.WriteEndElement(); } private void WriteCameraInstance(CameraInstance instance) { xml.WriteStartElement("instance_camera"); xml.WriteAttributeString("url", visitor.UrlOf(instance.Target)); xml.WriteEndElement(); } private void WriteGeometryInstance(GeometryInstance instance) { xml.WriteStartElement("instance_geometry"); xml.WriteAttributeString("url", visitor.UrlOf(instance.Target)); if (instance.Materials.Count > 0) { xml.WriteStartElement("bind_material"); xml.WriteStartElement("technique_common"); foreach (var matInstance in instance.Materials) WriteMaterialInstance(matInstance); xml.WriteEndElement(); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteMaterialInstance(MaterialInstance matInstance) { xml.WriteStartElement("instance_material"); xml.WriteAttributeString("symbol", matInstance.Symbol); xml.WriteAttributeString("target", visitor.UrlOf(matInstance.Target)); foreach (var binding in matInstance.Bindings) { xml.WriteStartElement("bind_vertex_input"); xml.WriteAttributeString("semantic", binding.Semantic); WriteSemanticAttribute("input_semantic", binding.VertexInput.Semantic); xml.WriteAttributeString("input_set", XmlConvert.ToString(binding.VertexInput.Set)); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteAnimation(Animation animation) { BeginEntity("animation", animation); foreach (var source in animation.Sources) WriteSource(source, Semantic.None); foreach (var sampler in animation.Samplers) WriteAnimationSampler(animation, sampler); foreach (var channel in animation.Channels) WriteAnimationChannel(channel); EndEntity(); } private void WriteAnimationSampler(Animation animation, Sampler sampler) { xml.WriteStartElement("sampler"); xml.WriteAttributeString("id", visitor.IdOf(sampler)); foreach (var input in sampler.Inputs) { xml.WriteStartElement("input"); WriteSemanticAttribute("semantic", input.Semantic); xml.WriteAttributeString("source", visitor.UrlOf(input.Source)); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteAnimationChannel(AnimationChannel channel) { xml.WriteStartElement("channel"); xml.WriteAttributeString("source", visitor.UrlOf(channel.Sampler)); xml.WriteAttributeString("target", channel.TargetPath); xml.WriteEndElement(); } private void BeginEntity(string name, Entity entity) { xml.WriteStartElement(name); string id = visitor.IdOf(entity); if (!string.IsNullOrEmpty(id)) xml.WriteAttributeString("id", id); //if (!String.IsNullOrEmpty(entity.Name)) // xml.WriteAttributeString("name", entity.Name); } private void EndEntity() { xml.WriteEndElement(); } private void WriteSource(Source source, Semantic semantic) { if (writtenSources.ContainsKey(source)) return; string sourceId = visitor.IdOf(source); writtenSources.Add(source, sourceId); var animationSource = source as AnimationSource; if (animationSource != null) { if (source.FloatData != null) WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, animationSource.Parameters); else WriteSource(sourceId, source.NameData, x => x, source.Stride, animationSource.Parameters); return; } switch (semantic) { case Semantic.Position: case Semantic.Normal: WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "X", "Y", "Z" }); break; case Semantic.TexCoord: WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "S", "T" }); break; case Semantic.Color: if (source.Stride == 4) WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "R", "G", "B", "A" }); else WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "R", "G", "B" }); break; case Semantic.Input: WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "TIME" }); return; case Semantic.Output: WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "VALUE" }); return; case Semantic.Interpolation: WriteSource(sourceId, source.NameData, x => x, source.Stride, new[] { "INTERPOLATION" }); return; case Semantic.InTangent: case Semantic.OutTangent: WriteSource(sourceId, source.FloatData, XmlConvert.ToString, source.Stride, new[] { "X", "Y" }); break; default: throw new NotSupportedException(string.Format("Sources with semantic {0} are not supported", semantic)); } } private void WriteSource(string sourceId, T[] data, Func toString, int stride, string[] paramNames) { string arrayId = sourceId + "_array"; string type = null; if (typeof(T) == typeof(float)) type = "float"; else if (typeof(T) == typeof(string)) type = "Name"; xml.WriteStartElement("source"); xml.WriteAttributeString("id", sourceId); xml.WriteStartElement(type + "_array"); xml.WriteAttributeString("id", arrayId); xml.WriteAttributeString("count", XmlConvert.ToString(data.Length)); xml.WriteWhitespace("\n"); int valuesPerLine = (stride == 1) ? 10 : stride; for (int i = 0; i < data.Length; i++) { xml.WriteString(toString(data[i])); if (i != data.Length - 1) { if (i % valuesPerLine == valuesPerLine - 1) xml.WriteWhitespace("\n"); else xml.WriteWhitespace(" "); } } xml.WriteEndElement(); xml.WriteStartElement("technique_common"); WriteSourceAccessor(arrayId, data.Length / stride, stride, type, paramNames); xml.WriteEndElement(); xml.WriteEndElement(); } private void WriteSourceAccessor(string arrayId, int count, int stride, string type, string[] paramNames) { xml.WriteStartElement("accessor"); xml.WriteAttributeString("source", "#" + arrayId); xml.WriteAttributeString("count", XmlConvert.ToString(count)); xml.WriteAttributeString("stride", XmlConvert.ToString(stride)); for (int i = 0; i < stride; i++) { xml.WriteStartElement("param"); xml.WriteAttributeString("type", type); xml.WriteAttributeString("name", paramNames[i]); xml.WriteEndElement(); } xml.WriteEndElement(); } private void WriteSemanticAttribute(string name, Semantic semantic) { xml.WriteAttributeString(name, semantic.ToString().ToUpperInvariant()); } private void WriteCamera(Camera camera) { BeginEntity("camera", camera); xml.WriteStartElement("optics"); xml.WriteStartElement("technique_common"); xml.WriteStartElement("perspective"); xml.WriteElementString("xfov", XmlConvert.ToString(camera.XFov)); xml.WriteElementString("aspect_ratio", XmlConvert.ToString(camera.AspectRatio)); xml.WriteElementString("znear", XmlConvert.ToString(camera.ZNear)); xml.WriteElementString("zfar", XmlConvert.ToString(camera.ZFar)); xml.WriteEndElement(); xml.WriteEndElement(); xml.WriteEndElement(); EndEntity(); } } }