using System; using System.Collections.Generic; using System.IO; using System.Globalization; using System.Xml; using Oni.Xml; namespace Oni.Dae.IO { internal class DaeReader { public static string[] CommandLineArgs; #region Private data private static readonly string[] emptyStrings = new string[0]; private static readonly char[] whiteSpaceChars = new char[] { ' ', '\t' }; private static readonly Func intConverter = XmlConvert.ToInt32; private static readonly Func floatConverter = XmlConvert.ToSingle; private TextWriter error; private TextWriter info; private Scene mainScene; private Dictionary entities; private XmlReader xml; private Axis upAxis = Axis.Y; private float unit = 1.0f; private List delayedBindActions; private Uri baseUrl; private string fileName; private List scenes = new List(); private List lights = new List(); private List animations = new List(); private List geometries = new List(); private List effects = new List(); private List materials = new List(); private List images = new List(); private List cameras = new List(); #endregion #region private class Animation private class Animation : Entity { private List animations; private readonly List samplers = new List(); public List Animations { get { if (animations == null) animations = new List(); return animations; } } public List Samplers => samplers; } #endregion public static Scene ReadFile(string filePath) { var reader = new DaeReader { baseUrl = new Uri("file://" + Path.GetDirectoryName(filePath).Replace('\\', '/').TrimEnd('/') + "/"), fileName = Path.GetFileName(filePath), delayedBindActions = new List(), error = Console.Error, info = Console.Out }; var settings = new XmlReaderSettings { IgnoreWhitespace = true, IgnoreProcessingInstructions = true, IgnoreComments = true }; using (reader.xml = XmlReader.Create(filePath, settings)) reader.ReadRoot(); return reader.mainScene; } private void ReadRoot() { while (xml.NodeType != XmlNodeType.Element) xml.Read(); if (xml.LocalName != "COLLADA") throw new InvalidDataException(string.Format("Unknown root element {0} found", xml.LocalName)); string version = xml.GetAttribute("version"); if (version != "1.4.0" && version != "1.4.1") throw new NotSupportedException(string.Format("Unsupported Collada file version {0}", version)); if (!xml.IsEmptyElement) { xml.ReadStartElement(); ReadAsset(); ReadContent(); ReadExtra(); } foreach (var action in delayedBindActions) action(); if (mainScene == null && scenes.Count > 0) mainScene = scenes[0]; BindNodes(mainScene); if (upAxis != Axis.Y) AxisConverter.Convert(mainScene, upAxis, Axis.Y); float scale = 1.0f; if (CommandLineArgs != null) { string scaleArg = Array.Find(CommandLineArgs, x => x.StartsWith("-dae-scale:", StringComparison.Ordinal)); if (scaleArg != null) scale = float.Parse(scaleArg.Substring(11), CultureInfo.InvariantCulture); } if (unit != 0.1f || scale != 1.0f) UnitConverter.Convert(mainScene, 10.0f * unit * scale); } private void ReadContent() { while (xml.IsStartElement()) { switch (xml.LocalName) { case "library_cameras": ReadLibrary(cameras, "camera", ReadCamera); break; case "library_images": ReadLibrary(images, "image", ReadImage); break; case "library_effects": ReadLibrary(effects, "effect", ReadEffect); break; case "library_materials": ReadLibrary(materials, "material", ReadMaterial); break; case "library_geometries": ReadLibrary(geometries, "geometry", ReadGeometry); break; case "library_nodes": ReadLibrary(scenes, "node", ReadNode); break; case "library_visual_scenes": ReadLibrary(scenes, "visual_scene", ReadScene); break; case "library_animations": ReadLibrary(animations, "animation", ReadAnimation); break; case "library_lights": ReadLibrary(lights, "light", ReadLight); break; case "scene": ReadScene(); break; default: xml.Skip(); break; } } } // // Libraries // private void ReadLibrary(ICollection library, string elementName, Action entityReader) where T : Entity, new() { if (xml.SkipEmpty()) return; xml.ReadStartElement(); ReadAsset(); while (xml.IsStartElement(elementName)) ReadEntity(library, entityReader); ReadExtra(); xml.ReadEndElement(); } private void ReadEntity(ICollection entityCollection, Action entityReader) where T : Entity, new() { T entity = ReadEntity(entityReader); entityCollection.Add(entity); } private T ReadEntity(Action entityReader) where T : Entity, new() { string id = xml.GetAttribute("id"); var entity = new T { Name = xml.GetAttribute("name"), FileName = fileName }; AddEntity(id, entity); if (string.IsNullOrEmpty(entity.Name)) entity.Name = id; if (xml.IsEmptyElement) { xml.ReadStartElement(); return entity; } xml.ReadStartElement(); ReadAsset(); entityReader(entity); ReadExtra(); xml.ReadEndElement(); return entity; } // // Cameras // private void ReadCamera(Camera camera) { ReadAsset(); xml.ReadStartElement("optics"); xml.ReadStartElement("technique_common"); if (xml.IsStartElement("perspective")) ReadCameraParameters(camera, CameraType.Perspective); else if (xml.IsStartElement("orthographic")) ReadCameraParameters(camera, CameraType.Orthographic); else if (xml.IsStartElement()) xml.Skip(); xml.ReadEndElement(); while (xml.IsStartElement()) xml.Skip(); xml.ReadEndElement(); if (xml.IsStartElement("imager")) xml.Skip(); ReadExtra(); } private void ReadCameraParameters(Camera camera, CameraType type) { xml.ReadStartElement(); camera.Type = type; while (xml.IsStartElement()) { switch (xml.LocalName) { case "xfov": camera.XFov = xml.ReadElementContentAsFloat(); break; case "yfov": camera.YFov = xml.ReadElementContentAsFloat(); break; case "xmag": camera.XMag = xml.ReadElementContentAsFloat(); break; case "ymag": camera.YMag = xml.ReadElementContentAsFloat(); break; case "aspect_ratio": camera.AspectRatio = xml.ReadElementContentAsFloat(); break; case "znear": camera.ZNear = xml.ReadElementContentAsFloat(); break; case "zfar": camera.ZFar = xml.ReadElementContentAsFloat(); break; default: xml.Skip(); break; } } xml.ReadEndElement(); } // // Materials // private void ReadImage(Image image) { //image.Width = ReadNullableIntAttribute("width"); //image.Height = ReadNullableIntAttribute("height"); //image.Depth = ReadNullableIntAttribute("depth"); ReadAsset(); if (xml.IsStartElement("init_from")) { string filePath = xml.ReadElementContentAsString(); if (!string.IsNullOrEmpty(filePath)) { var imageUri = new Uri(baseUrl, filePath); image.FilePath = imageUri.LocalPath; } } else if (xml.IsStartElement("data")) { throw new NotSupportedException("Embedded image data is not supported"); } else { throw new InvalidDataException(); } ReadExtra(); } private void ReadEffect(Effect effect) { while (xml.IsStartElement()) { switch (xml.LocalName) { case "image": ReadEntity(images, ReadImage); break; case "newparam": ReadEffectParameterDecl(effect); break; case "profile_COMMON": ReadEffectProfileCommon(effect); break; default: xml.Skip(); break; } } ReadExtra(); } private void ReadEffectProfileCommon(Effect effect) { xml.ReadStartElement(); ReadAsset(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "image": ReadEntity(images, ReadImage); break; case "newparam": ReadEffectParameterDecl(effect); break; case "technique": ReadEffectTechniqueCommon(effect); break; default: xml.Skip(); break; } } ReadExtra(); xml.ReadEndElement(); } private void ReadEffectTechniqueCommon(Effect effect) { xml.ReadStartElement(); ReadAsset(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "image": ReadEntity(images, ReadImage); break; case "constant": case "lambert": case "phong": case "blinn": xml.ReadStartElement(); ReadEffectTechniqueParameters(effect); xml.ReadEndElement(); break; default: xml.Skip(); break; } } ReadExtra(); xml.ReadEndElement(); } private void ReadEffectParameterDecl(Effect effect) { var parameter = new EffectParameter(); parameter.Sid = xml.GetAttribute("sid"); xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "semantic": parameter.Semantic = xml.ReadElementContentAsString(); break; case "float": parameter.Value = xml.ReadElementContentAsFloat(); break; case "float2": parameter.Value = xml.ReadElementContentAsVector2(); break; case "float3": parameter.Value = xml.ReadElementContentAsVector3(); break; case "surface": parameter.Value = ReadEffectSurface(effect); break; case "sampler2D": parameter.Value = ReadEffectSampler2D(effect); break; default: xml.Skip(); break; } } xml.ReadEndElement(); effect.Parameters.Add(parameter); } private EffectSurface ReadEffectSurface(Effect effect) { var surface = new EffectSurface(); xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "init_from": BindId(xml.ReadElementContentAsString(), image => { surface.InitFrom = image; }); break; default: xml.Skip(); break; } } xml.ReadEndElement(); return surface; } private EffectSampler ReadEffectSampler2D(Effect effect) { var sampler = new EffectSampler(); xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "source": string surfaceSid = xml.ReadElementContentAsString(); foreach (var parameter in effect.Parameters) { if (parameter.Sid == surfaceSid) sampler.Surface = parameter.Value as EffectSurface; } break; case "wrap_s": sampler.WrapS = (EffectSamplerWrap)Enum.Parse(typeof(EffectSamplerWrap), xml.ReadElementContentAsString(), true); break; case "wrap_t": sampler.WrapT = (EffectSamplerWrap)Enum.Parse(typeof(EffectSamplerWrap), xml.ReadElementContentAsString(), true); break; default: xml.Skip(); break; } } xml.ReadEndElement(); return sampler; } private void ReadEffectTechniqueParameters(Effect effect) { while (xml.IsStartElement()) { switch (xml.LocalName) { case "emission": ReadColorEffectParameter(effect, effect.Emission, EffectTextureChannel.Emission); break; case "ambient": ReadColorEffectParameter(effect, effect.Ambient, EffectTextureChannel.Ambient); break; case "diffuse": ReadColorEffectParameter(effect, effect.Diffuse, EffectTextureChannel.Diffuse); break; case "specular": ReadColorEffectParameter(effect, effect.Specular, EffectTextureChannel.Specular); break; case "shininess": ReadFloatEffectParameter(effect.Shininess); break; case "reflective": ReadColorEffectParameter(effect, effect.Reflective, EffectTextureChannel.Reflective); break; case "reflectivity": ReadFloatEffectParameter(effect.Reflectivity); break; case "transparent": ReadColorEffectParameter(effect, effect.Transparent, EffectTextureChannel.Transparent); break; case "transparency": ReadFloatEffectParameter(effect.Transparency); break; case "index_of_refraction": ReadFloatEffectParameter(effect.IndexOfRefraction); break; default: xml.Skip(); break; } } } private void ReadFloatEffectParameter(EffectParameter parameter) { xml.ReadStartElement(); if (xml.IsStartElement("float")) { parameter.Sid = xml.GetAttribute("sid"); parameter.Value = xml.ReadElementContentAsFloat(); } else if (xml.IsStartElement("param")) { parameter.Reference = xml.GetAttribute("ref"); xml.Skip(); } xml.ReadEndElement(); } private void ReadColorEffectParameter(Effect effect, EffectParameter parameter, EffectTextureChannel channel) { xml.ReadStartElement(); if (xml.IsStartElement("color")) { parameter.Sid = xml.GetAttribute("sid"); parameter.Value = xml.ReadElementContentAsVector4(); } else if (xml.IsStartElement("param")) { parameter.Sid = null; parameter.Reference = xml.GetAttribute("ref"); } else if (xml.IsStartElement("texture")) { parameter.Sid = null; string texCoordSymbol = xml.GetAttribute("texcoord"); string samplerId = xml.GetAttribute("texture"); xml.Skip(); // // HACK: Maya produces duplicate texture elements, skip them... // while (xml.IsStartElement("texture")) xml.Skip(); EffectSampler sampler = null; foreach (var parameterDecl in effect.Parameters) { if (parameterDecl.Sid == samplerId) { sampler = parameterDecl.Value as EffectSampler; break; } } if (sampler == null) { info.WriteLine("COLLADA: cannot find sampler {0} in effect {1}, trying to use image directly", samplerId, effect.Name); var surface = new EffectSurface(); sampler = new EffectSampler(surface); BindId(samplerId, image => { surface.InitFrom = image; }); } var texture = new EffectTexture { Channel = channel, TexCoordSemantic = texCoordSymbol, Sampler = sampler }; parameter.Value = texture; } xml.ReadEndElement(); } private void ReadMaterial(Material material) { if (!xml.IsStartElement("instance_effect")) return; BindUrlAttribute("url", effect => { material.Effect = effect; }); xml.Skip(); } // // Geometry // private void ReadGeometry(Geometry geometry) { if (xml.IsStartElement("mesh")) { ReadMesh(geometry); } else { throw new NotSupportedException(string.Format("Geometry content of type {0} is not supported", xml.LocalName)); } } // // Mesh // private void ReadMesh(Geometry geometry) { xml.ReadStartElement(); while (xml.IsStartElement("source")) ReadGeometrySource(); if (xml.IsStartElement("vertices")) ReadMeshVertices(geometry); while (xml.IsStartElement()) { var primitives = ReadMeshPrimitives(geometry); if (primitives == null) break; geometry.Primitives.Add(primitives); } ReadExtra(); xml.ReadEndElement(); } // // Mesh Data Source // private Source ReadGeometrySource() { string id = xml.GetAttribute("id"); string name = xml.GetAttribute("name"); xml.ReadStartElement(); ReadAsset(); var data = ReadFloatArray(); var source = new Source(data, 1) { Name = name }; if (xml.IsStartElement("technique_common")) { xml.ReadStartElement(); if (xml.IsStartElement("accessor")) { source.Stride = ReadIntAttribute("stride", 1); xml.ReadStartElement(); while (xml.IsStartElement("param")) ReadParam(); xml.ReadEndElement(); } xml.ReadEndElement(); } xml.SkipSequence("technique"); xml.ReadEndElement(); AddEntity(id, source); return source; } private string ReadParam() { string name = xml.GetAttribute("name"); //string sid = xml.GetAttribute("sid"); //string type = xml.GetAttribute("type"); //string semantic = xml.GetAttribute("semantic"); xml.Skip(); return name; } // // Mesh Vertices // private void ReadMeshVertices(Geometry mesh) { string id = xml.GetAttribute("id"); for (xml.ReadStartElement(); xml.IsStartElement("input"); xml.Skip()) { var semantic = ReadSemanticAttribute(); if (semantic != Semantic.None && semantic != Semantic.Vertex) { var input = new Input(); input.Semantic = semantic; BindUrlAttribute("source", s => { input.Source = s; }); mesh.Vertices.Add(input); } } ReadExtra(); xml.ReadEndElement(); } // // Mesh Polygons // private MeshPrimitives ReadMeshPrimitives(Geometry mesh) { MeshPrimitives primitives; int primitiveCount = ReadIntAttribute("count", 0); int fixedVertexCount = 0; bool isPolygons = false; switch (xml.LocalName) { case "lines": primitives = new MeshPrimitives(MeshPrimitiveType.Lines); fixedVertexCount = 2; break; case "triangles": primitives = new MeshPrimitives(MeshPrimitiveType.Polygons); fixedVertexCount = 3; break; case "linestrips": primitives = new MeshPrimitives(MeshPrimitiveType.LineStrips); isPolygons = true; break; case "trifans": primitives = new MeshPrimitives(MeshPrimitiveType.TriangleFans); isPolygons = true; break; case "tristrips": primitives = new MeshPrimitives(MeshPrimitiveType.TriangleStrips); isPolygons = true; break; case "polygons": primitives = new MeshPrimitives(MeshPrimitiveType.Polygons); isPolygons = true; break; case "polylist": primitives = new MeshPrimitives(MeshPrimitiveType.Polygons); break; default: return null; } primitives.MaterialSymbol = xml.GetAttribute("material"); bool vertexFound = false; // // Read the inputs // for (xml.ReadStartElement(); xml.IsStartElement("input"); xml.Skip()) { var semantic = ReadSemanticAttribute(); if (semantic == Semantic.None) continue; int offset = ReadIntAttribute("offset"); string sourceId = xml.GetAttribute("source"); int set = ReadIntAttribute("set", -1); if (semantic == Semantic.Vertex) { if (vertexFound) { error.WriteLine("Duplicate vertex input found"); continue; } vertexFound = true; foreach (var vertexInput in mesh.Vertices) { primitives.Inputs.Add(new IndexedInput { Source = vertexInput.Source, Offset = offset, Set = set, Semantic = vertexInput.Semantic }); } } else { var input = new IndexedInput { Offset = offset, Semantic = semantic, Set = set }; BindUrl(sourceId, s => { // // Ignore inputs with no source data // if (s.Count > 0) { input.Source = s; primitives.Inputs.Add(input); } }); } } if (!vertexFound) throw new InvalidDataException("no vertex input"); // // Read vertex counts (if availabled and needed) // if (primitiveCount > 0) primitives.VertexCounts.Capacity = primitiveCount; int numIndices = 0; while (xml.IsStartElement("vcount")) { if (fixedVertexCount != 0 || isPolygons) { xml.Skip(); continue; } foreach (var token in xml.ReadElementContentAsList()) { int count = XmlConvert.ToInt32(token); numIndices += count; primitives.VertexCounts.Add(count); } } if (fixedVertexCount != 0) { for (int i = 0; i < primitiveCount; i++) primitives.VertexCounts.Add(fixedVertexCount); numIndices = fixedVertexCount * primitiveCount; } else if (!isPolygons) { if (primitives.VertexCounts.Count == 0) throw new InvalidDataException("no vcount"); } // // Read input indices // 1. Collect all inputs in an array indexed by input offset // var maxOffset = primitives.Inputs.Max(x => x.Offset); var inputIndices = new List[maxOffset + 1]; foreach (var input in primitives.Inputs) { List indices = inputIndices[input.Offset]; if (indices == null) { indices = new List(numIndices); inputIndices[input.Offset] = indices; } } // // 2. Read polygon input indices // if (!isPolygons) { while (xml.IsStartElement("p")) ReadInterleavedInputIndices(inputIndices); } else { while (xml.IsStartElement()) { if (xml.IsStartElement("p")) { primitives.VertexCounts.Add(ReadInterleavedInputIndices(inputIndices)); } else if (xml.IsStartElement("ph")) { xml.ReadStartElement(); while (xml.IsStartElement()) { if (xml.LocalName == "p") primitives.VertexCounts.Add(ReadInterleavedInputIndices(inputIndices)); else xml.Skip(); } xml.ReadEndElement(); } else { break; } } } foreach (var input in primitives.Inputs) input.Indices.AddRange(inputIndices[input.Offset]); ReadExtra(); xml.ReadEndElement(); return primitives; } private int ReadInterleavedInputIndices(List[] inputs) { int count = 0; int offset = 0; foreach (string token in xml.ReadElementContentAsList()) { var input = inputs[offset++]; if (input != null) input.Add(XmlConvert.ToInt32(token)); if (offset >= inputs.Length) { offset = 0; count++; } } return count; } // // Scene // private void ReadScene(Scene scene) { while (xml.IsStartElement("node")) ReadEntity(scene.Nodes, ReadNode); } private void ReadNode(Node node) { ReadTransforms(node.Transforms); while (xml.IsStartElement()) { switch (xml.LocalName) { case "node": ReadEntity(node.Nodes, ReadNode); break; case "instance_geometry": node.Instances.Add(ReadGeometryInstance()); break; case "instance_light": node.Instances.Add(ReadLightInstance()); break; case "instance_camera": node.Instances.Add(ReadCameraInstance()); break; case "instance_node": node.Instances.Add(ReadNodeInstance()); break; default: xml.Skip(); break; } } } private void ReadSimpleInstance(Instance instance) where T : Entity { instance.Sid = xml.GetAttribute("sid"); instance.Name = xml.GetAttribute("name"); BindUrlAttribute("url", camera => { instance.Target = camera; }); xml.Skip(); } private NodeInstance ReadNodeInstance() { var instance = new NodeInstance(); ReadSimpleInstance(instance); return instance; } private CameraInstance ReadCameraInstance() { var instance = new CameraInstance(); ReadSimpleInstance(instance); return instance; } private LightInstance ReadLightInstance() { var instance = new LightInstance(); ReadSimpleInstance(instance); return instance; } // // Node Transforms // private void ReadTransforms(ICollection transforms) { while (xml.IsStartElement()) { Transform transform = null; switch (xml.LocalName) { case "matrix": transform = new TransformMatrix(); break; case "rotate": transform = new TransformRotate(); break; case "scale": transform = new TransformScale(); break; case "translate": transform = new TransformTranslate(); break; case "skew": case "lookat": xml.Skip(); break; default: return; } if (transform != null) { transform.Sid = xml.GetAttribute("sid"); xml.ReadElementContentAsArray(floatConverter, transform.Values); transforms.Add(transform); } } } // // Instances // private void ReadInstances(ICollection instances) { while (xml.IsStartElement()) { switch (xml.LocalName) { case "instance_geometry": instances.Add(ReadGeometryInstance()); break; case "instance_camera": case "instance_controller": case "instance_light": case "instance_node": xml.Skip(); break; default: return; } } } private GeometryInstance ReadGeometryInstance() { var instance = new GeometryInstance { Name = xml.GetAttribute("name"), Sid = xml.GetAttribute("sid"), }; string url = xml.GetAttribute("url"); BindUrl(url, geometry => { instance.Target = geometry; }); if (!xml.SkipEmpty()) { xml.ReadStartElement(); if (xml.IsStartElement("bind_material")) ReadBindMaterial(instance, url); ReadExtra(); xml.ReadEndElement(); } return instance; } private void ReadBindMaterial(GeometryInstance geometryInstance, string geometryUrl) { xml.ReadStartElement(); while (xml.IsStartElement()) { if (xml.LocalName != "technique_common") { xml.Skip(); continue; } xml.ReadStartElement(); while (xml.IsStartElement()) { if (xml.LocalName == "instance_material") ReadMaterialInstance(geometryInstance, geometryUrl); else xml.Skip(); } xml.ReadEndElement(); } xml.ReadEndElement(); } private void ReadMaterialInstance(GeometryInstance geometryInstance, string geometryUrl) { var instance = new MaterialInstance(); instance.Symbol = xml.GetAttribute("symbol"); BindUrlAttribute("target", material => { instance.Target = material; }); geometryInstance.Materials.Add(instance); if (xml.SkipEmpty()) return; for (xml.ReadStartElement(); xml.IsStartElement(); xml.Skip()) { if (xml.LocalName == "bind") { var binding = new MaterialBinding(); binding.Semantic = xml.GetAttribute("semantic"); string target = xml.GetAttribute("target"); BindId(target, s => { BindUrl(geometryUrl, g => { var primitives = g.Primitives.Find(p => p.MaterialSymbol == instance.Symbol); if (primitives == null) return; var input = primitives.Inputs.Find(i => i.Source == s); if (input == null) return; binding.VertexInput = input; instance.Bindings.Add(binding); }); }); } else if (xml.LocalName == "bind_vertex_input") { var binding = new MaterialBinding(); binding.Semantic = xml.GetAttribute("semantic"); var inputSemantic = ReadSemanticAttribute("input_semantic"); int inputSet = ReadIntAttribute("input_set", 0); BindUrl(geometryUrl, g => { var primitives = g.Primitives.Find(p => p.MaterialSymbol == instance.Symbol); if (primitives == null) return; var input = primitives.Inputs.Find(i => i.Semantic == inputSemantic && i.Set == inputSet); if (input == null) return; binding.VertexInput = input; instance.Bindings.Add(binding); }); } } xml.ReadEndElement(); } // // Animations // private void ReadAnimation(Animation animation) { while (xml.IsStartElement("animation")) ReadEntity(animation.Animations, ReadAnimation); while (xml.IsStartElement("source")) ReadAnimationSource(); while (xml.IsStartElement("sampler")) animation.Samplers.Add(ReadAnimationSampler()); while (xml.IsStartElement("channel")) { BindAnimationSampler(xml.GetAttribute("source"), xml.GetAttribute("target")); xml.Skip(); } } private Source ReadAnimationSource() { string id = xml.GetAttribute("id"); string name = xml.GetAttribute("name"); if (string.IsNullOrEmpty(name)) name = id; xml.ReadStartElement(); ReadAsset(); Source source; if (xml.IsStartElement("float_array")) source = new Source(ReadFloatArray(), 1); else if (xml.IsStartElement("Name_array")) source = new Source(ReadNameArray(), 1); else throw new NotSupportedException(string.Format("Animation sources of type {0} are not supported", xml.LocalName)); source.Name = name; if (xml.IsStartElement("technique_common")) { xml.ReadStartElement(); if (xml.IsStartElement("accessor")) { source.Stride = ReadIntAttribute("stride", 1); xml.ReadStartElement(); while (xml.IsStartElement("param")) ReadParam(); xml.ReadEndElement(); } xml.ReadEndElement(); } xml.SkipSequence("technique"); xml.ReadEndElement(); AddEntity(id, source); return source; } private Input ReadAnimationInput() { var input = new Input(); input.Semantic = ReadSemanticAttribute(); BindUrlAttribute("source", source => { input.Source = source; }); xml.Skip(); return input; } private Sampler ReadAnimationSampler() { string id = xml.GetAttribute("id"); xml.ReadStartElement(); var sampler = new Sampler(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "input": sampler.Inputs.Add(ReadAnimationInput()); break; default: xml.Skip(); break; } } xml.ReadEndElement(); AddEntity(id, sampler); return sampler; } #region private class TargetPath private class TargetPath { private string nodeId; private string[] path; private string value; private TargetPath() { } public static TargetPath Parse(string text) { TargetPath path = new TargetPath(); List sids = new List(); int index = text.IndexOf('/'); if (index == -1) index = text.Length; path.nodeId = text.Substring(0, index); for (int start = index + 1; start < text.Length; start = index + 1) { index = text.IndexOf('/', start); if (index == -1) { index = text.IndexOf('.', start); if (index == -1) { sids.Add(text.Substring(start)); } else { sids.Add(text.Substring(start, index - start)); path.value = text.Substring(index + 1); } break; } sids.Add(text.Substring(start, index - start)); } if (sids.Count > 0) path.path = sids.ToArray(); return path; } public string NodeId => nodeId; public string[] Path => path; public string Value => value; } #endregion // // Lights // private void ReadLight(Light light) { if (!xml.IsStartElement("technique_common")) { xml.Skip(); return; } xml.ReadStartElement(); if (xml.IsStartElement()) { switch (xml.LocalName) { case "ambient": light.Type = LightType.Ambient; break; case "directional": light.Type = LightType.Directional; break; case "point": light.Type = LightType.Point; break; case "spot": light.Type = LightType.Spot; break; } xml.ReadStartElement(); light.Color = xml.ReadElementContentAsVector3("color"); if (light.Type == LightType.Point || light.Type == LightType.Spot) { if (xml.LocalName == "constant_attenuation") light.ConstantAttenuation = xml.ReadElementContentAsFloat(); if (xml.LocalName == "linear_attenuation") light.LinearAttenuation = xml.ReadElementContentAsFloat(); if (light.Type == LightType.Point) { light.QuadraticAttenuation = xml.ReadElementContentAsFloat("quadratic_attenuation", string.Empty); if (xml.LocalName == "zfar") light.ZFar = xml.ReadElementContentAsFloat(); } else if (light.Type == LightType.Spot) { if (xml.LocalName == "quadratic_attenuation") light.QuadraticAttenuation = xml.ReadElementContentAsFloat(); if (xml.LocalName == "falloff_angle") light.FalloffAngle = xml.ReadElementContentAsFloat(); if (xml.LocalName == "falloff_exponent") light.FalloffExponent = xml.ReadElementContentAsFloat(); } } xml.ReadEndElement(); } xml.ReadEndElement(); } // // Scene // private void ReadScene() { if (!xml.IsStartElement("scene")) return; xml.ReadStartElement(); xml.SkipSequence("instance_physics_scene"); if (xml.IsStartElement("instance_visual_scene")) { BindUrlAttribute("url", scene => { mainScene = scene; }); xml.Skip(); } ReadExtra(); xml.ReadEndElement(); } // // Others // private void ReadAsset() { if (!xml.IsStartElement("asset")) return; xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "up_axis": upAxis = ReadUpAxis(); break; case "contributor": ReadAssetContributor(); break; case "unit": unit = XmlConvert.ToSingle(xml.GetAttribute("meter")); xml.Skip(); break; default: xml.Skip(); break; } } xml.ReadEndElement(); } private void ReadAssetContributor() { xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { default: xml.Skip(); break; } } xml.ReadEndElement(); } private void ReadExtra() { while (xml.IsStartElement("extra")) xml.Skip(); } // // Arrays // private float[] ReadFloatArray() { if (!xml.IsStartElement("float_array")) return null; var id = xml.GetAttribute("id"); var count = ReadIntAttribute("count"); var data = new float[count]; var index = 0; foreach (var token in xml.ReadElementContentAsList()) { if (index < data.Length) data[index++] = XmlConvert.ToSingle(token); } return data; } private string[] ReadNameArray() { if (!xml.IsStartElement("Name_array")) return null; var id = xml.GetAttribute("id"); var count = ReadIntAttribute("count"); var data = new string[count]; var index = 0; foreach (var token in xml.ReadElementContentAsList()) { if (index < data.Length) data[index++] = token; } return data; } // // Attributes // private int ReadIntAttribute(string name) { string text = xml.GetAttribute(name); if (string.IsNullOrEmpty(text)) throw new InvalidDataException(name + " attribute not found"); return XmlConvert.ToInt32(text); } private int ReadIntAttribute(string name, int defaultValue) { string text = xml.GetAttribute(name); if (string.IsNullOrEmpty(text)) return defaultValue; return XmlConvert.ToInt32(text); } private int? ReadNullableIntAttribute(string name) { string text = xml.GetAttribute(name); if (string.IsNullOrEmpty(text)) return null; return XmlConvert.ToInt32(text); } private Semantic ReadSemanticAttribute() { return ReadSemanticAttribute("semantic"); } private Semantic ReadSemanticAttribute(string name) { string text = xml.GetAttribute(name); switch (text) { case "POSITION": return Semantic.Position; case "TEXCOORD": return Semantic.TexCoord; case "NORMAL": return Semantic.Normal; case "COLOR": return Semantic.Color; case "VERTEX": return Semantic.Vertex; case "INPUT": return Semantic.Input; case "IN_TANGENT": return Semantic.InTangent; case "OUT_TANGENT": return Semantic.OutTangent; case "INTERPOLATION": return Semantic.Interpolation; case "OUTPUT": return Semantic.Output; default: return Semantic.None; } } private string[] ReadStringListAttribute(string name) { string text = xml.GetAttribute(name); if (string.IsNullOrEmpty(text)) return emptyStrings; return text.Split(whiteSpaceChars, StringSplitOptions.RemoveEmptyEntries); } private Axis ReadUpAxis() { switch (xml.ReadElementContentAsString()) { case "X_UP": return Axis.X; case "Z_UP": return Axis.Z; default: return Axis.Y; } } private void AddEntity(string id, Entity entity) { if (string.IsNullOrEmpty(id)) return; if (entities == null) entities = new Dictionary(); if (entities.ContainsKey(id)) error.WriteLine(string.Format("COLLADA error: duplicate id {0}", id)); else entities.Add(id, entity); entity.Id = id; } private T GetEntity(string id) where T : Entity { Entity result; if (entities == null || string.IsNullOrEmpty(id) || !entities.TryGetValue(id, out result)) return null; return result as T; } private void BindUrlAttribute(string name, Action action) where T : Entity { BindUrl(xml.GetAttribute(name), action); } private void BindUrl(string url, Action action) where T : Entity { if (string.IsNullOrEmpty(url)) return; if (url[0] != '#') throw new NotSupportedException(string.Format("External reference '{0}' is not supported", url)); BindId(url.Substring(1), action); } private void BindId(string id, Action action) where T : Entity { if (string.IsNullOrEmpty(id)) return; var entity = GetEntity(id); if (entity != null) { action(entity); return; } delayedBindActions.Add(delegate { var entity2 = GetEntity(id); if (entity2 != null) action(entity2); }); } private void BindAnimationSampler(string sourceId, string targetPath) { var path = TargetPath.Parse(targetPath); BindUrlAttribute("source", sampler => { var output = sampler.Inputs.Find(i => i.Semantic == Semantic.Output); if (output == null || output.Source == null) return; int stride = output.Source.Stride; if (stride != 1) { for (int offset = 0; offset < stride; offset++) { var newSampler = sampler.Split(offset); BindId(path.NodeId, node => { Transform transform = FindTransform(node, path.Path[0]); if (transform != null) transform.BindAnimation(string.Format(CultureInfo.InvariantCulture, "({0})", offset), newSampler); }); } } else { BindId(path.NodeId, node => { var transform = FindTransform(node, path.Path[0]); if (transform != null) transform.BindAnimation(path.Value, sampler); }); } }); } private Transform FindTransform(Node node, string sid) { return node.Transforms.Find(t => t.Sid == sid); } private void BindNodes(Node node) { foreach (var instance in node.Instances.OfType().ToList()) { node.Instances.Remove(instance); if (instance.Target != node) node.Nodes.Add(instance.Target); } foreach (var child in node.Nodes) BindNodes(child); } } }