using System; using System.Collections.Generic; using System.IO; using System.Xml; using Oni.Motoko; using Oni.Physics; using Oni.Totoro; namespace Oni { internal class SceneExporter { private readonly InstanceFileManager fileManager; private readonly string outputDirPath; private readonly TextureDaeWriter textureWriter; private readonly GeometryDaeWriter geometryWriter; private readonly BodyDaeWriter bodyWriter; private string basePath; private class SceneNode { public string Name; public readonly List Geometries = new List(); public readonly List Animations = new List(); public readonly List Nodes = new List(); public Body Body; public bool IsCamera; } private class SceneNodeAnimation { public int Start; public ObjectAnimation ObjectAnimation; } public SceneExporter(InstanceFileManager fileManager, string outputDirPath) { this.fileManager = fileManager; this.outputDirPath = outputDirPath; textureWriter = new TextureDaeWriter(outputDirPath); geometryWriter = new GeometryDaeWriter(textureWriter); bodyWriter = new BodyDaeWriter(geometryWriter); } public void ExportScene(string sourceFilePath) { basePath = Path.GetDirectoryName(sourceFilePath); var scene = new Dae.Scene(); var settings = new XmlReaderSettings { IgnoreWhitespace = true, IgnoreProcessingInstructions = true, IgnoreComments = true }; var nodes = new List(); using (var xml = XmlReader.Create(sourceFilePath, settings)) { scene.Name = xml.GetAttribute("Name"); xml.ReadStartElement("Scene"); while (xml.IsStartElement()) nodes.Add(ReadNode(xml)); xml.ReadEndElement(); } foreach (var node in nodes) { scene.Nodes.Add(WriteNode(node, null)); } Dae.Writer.WriteFile(Path.Combine(outputDirPath, Path.GetFileNameWithoutExtension(sourceFilePath)) + ".dae", scene); } private string ResolvePath(string path) { return Path.Combine(basePath, path); } private SceneNode ReadNode(XmlReader xml) { var node = new SceneNode { Name = xml.GetAttribute("Name") }; xml.ReadStartElement("Node"); while (xml.IsStartElement()) { switch (xml.LocalName) { case "Geometry": ReadGeometry(xml, node); break; case "Body": ReadBody(xml, node); break; case "Camera": ReadCamera(xml, node); break; case "Animation": ReadAnimation(xml, node); break; case "Node": node.Nodes.Add(ReadNode(xml)); break; default: Console.WriteLine("Unknown element name {0}", xml.LocalName); xml.Skip(); break; } } xml.ReadEndElement(); return node; } private void ReadGeometry(XmlReader xml, SceneNode node) { var file = fileManager.OpenFile(ResolvePath(xml.ReadElementContentAsString())); var geometry = GeometryDatReader.Read(file.Descriptors[0]); node.Geometries.Add(geometry); } private void ReadBody(XmlReader xml, SceneNode node) { var file = fileManager.OpenFile(ResolvePath(xml.ReadElementContentAsString())); var body = BodyDatReader.Read(file.Descriptors[0]); node.Body = body; ReadBodyNode(node, body.Root); } private static void ReadBodyNode(SceneNode node, BodyNode bodyNode) { node.Name = bodyNode.Name; node.Geometries.Add(bodyNode.Geometry); foreach (var childBodyNode in bodyNode.Nodes) { var childNode = new SceneNode(); node.Nodes.Add(childNode); ReadBodyNode(childNode, childBodyNode); } } private void ReadAnimation(XmlReader xml, SceneNode node) { var startValue = xml.GetAttribute("Start"); var isMax = xml.GetAttribute("Type") == "Max"; var noRotation = xml.GetAttribute("NoRotation") == "true"; var filePath = xml.ReadElementContentAsString(); var start = string.IsNullOrEmpty(startValue) ? 0 : int.Parse(startValue); var file = fileManager.OpenFile(ResolvePath(filePath)); if (node.Body != null) { var animations = AnimationDatReader.Read(file.Descriptors[0]).ToObjectAnimation(node.Body); ReadBodyAnimation(start, node, node.Body.Root, animations); } else { node.Animations.Add(new SceneNodeAnimation { Start = start, ObjectAnimation = ObjectDatReader.ReadAnimation(file.Descriptors[0]) }); if (noRotation) { foreach (var key in node.Animations.Last().ObjectAnimation.Keys) key.Rotation = Quaternion.Identity; } else if (isMax) { foreach (var key in node.Animations.Last().ObjectAnimation.Keys) key.Rotation *= Quaternion.CreateFromAxisAngle(Vector3.UnitX, MathHelper.HalfPi); } } } private void ReadBodyAnimation(int start, SceneNode node, BodyNode bodyNode, ObjectAnimation[] animations) { node.Animations.Add(new SceneNodeAnimation { Start = start, ObjectAnimation = animations[bodyNode.Index] }); for (int i = 0; i < node.Nodes.Count; i++) ReadBodyAnimation(start, node.Nodes[i], bodyNode.Nodes[i], animations); } private void ReadCamera(XmlReader xml, SceneNode node) { node.IsCamera = true; xml.Skip(); } private Dae.Node WriteNode(SceneNode node, List parentFrames) { var daeNode = new Dae.Node { Name = node.Name }; foreach (var geometry in node.Geometries) daeNode.Instances.Add(geometryWriter.WriteGeometryInstance(geometry, geometry.Name)); if (node.IsCamera) WriteCamera(daeNode); List frames = null; if (node.Animations.Count > 0) { frames = BuildFrames(node); WriteAnimation(daeNode, BuildLocalFrames(node.Body == null ? parentFrames : null, frames)); } foreach (var child in node.Nodes) daeNode.Nodes.Add(WriteNode(child, frames)); return daeNode; } private static List BuildFrames(SceneNode node) { var frames = new List(); foreach (var animation in node.Animations) { var animFrames = animation.ObjectAnimation.Interpolate(); var start = animation.Start; if (frames.Count > 0) start += frames.Last().Time + 1; foreach (var frame in animFrames) frame.Time += start; if (frames.Count > 0) { while (frames.Last().Time >= animFrames.First().Time) { frames.RemoveAt(frames.Count - 1); } while (frames.Last().Time + 1 < animFrames.First().Time) { frames.Add(new ObjectAnimationKey { Time = frames.Last().Time + 1, Rotation = frames.Last().Rotation, Translation = frames.Last().Translation, Scale = frames.Last().Scale }); } } frames.AddRange(animFrames); } return frames; } private static List BuildLocalFrames(List parentFrames, List frames) { var localFrames = frames; if (parentFrames != null) { localFrames = new List(localFrames.Count); for (int i = 0; i < frames.Count; i++) { var frame = frames[i]; var parentFrame = parentFrames[i]; localFrames.Add(new ObjectAnimationKey { Time = frame.Time, Scale = frame.Scale / parentFrame.Scale, Rotation = Quaternion.Conjugate(parentFrame.Rotation) * frame.Rotation, Translation = Vector3.Transform(frame.Translation - parentFrame.Translation, parentFrame.Rotation.Inverse()) / parentFrame.Scale }); } } return localFrames; } private static void WriteAnimation(Dae.Node node, List frames) { var times = new float[frames.Count]; var interpolations = new string[times.Length]; var positions = new Vector3[frames.Count]; var angles = new Vector3[frames.Count]; for (int i = 0; i < times.Length; ++i) times[i] = frames[i].Time / 60.0f; for (int i = 0; i < interpolations.Length; i++) interpolations[i] = "LINEAR"; for (int i = 0; i < frames.Count; i++) positions[i] = frames[i].Translation; for (int i = 0; i < frames.Count; i++) angles[i] = frames[i].Rotation.ToEulerXYZ(); var translate = node.Transforms.Translate("translate", positions[0]); ; var rotateX = node.Transforms.Rotate("rotX", Vector3.UnitX, angles[0].X); var rotateY = node.Transforms.Rotate("rotY", Vector3.UnitY, angles[0].Y); var rotateZ = node.Transforms.Rotate("rotZ", Vector3.UnitZ, angles[0].Z); var scale = node.Transforms.Scale("scale", frames[0].Scale); WriteSampler(times, interpolations, i => positions[i].X, translate, "X"); WriteSampler(times, interpolations, i => positions[i].Y, translate, "Y"); WriteSampler(times, interpolations, i => positions[i].Z, translate, "Z"); WriteSampler(times, interpolations, i => angles[i].X, rotateX, "ANGLE"); WriteSampler(times, interpolations, i => angles[i].Y, rotateY, "ANGLE"); WriteSampler(times, interpolations, i => angles[i].Z, rotateZ, "ANGLE"); } private static void WriteSampler(float[] times, string[] interpolations, Func getValue, Dae.Transform transform, string targetName) { var values = new float[times.Length]; for (int i = 0; i < values.Length; ++i) values[i] = getValue(i); transform.BindAnimation(targetName, new Dae.Sampler { Inputs = { new Dae.Input(Dae.Semantic.Input, new Dae.Source(times, 1)), new Dae.Input(Dae.Semantic.Output, new Dae.Source(values, 1)), new Dae.Input(Dae.Semantic.Interpolation, new Dae.Source(interpolations, 1)) } }); } private static void WriteCamera(Dae.Node daeNode) { daeNode.Instances.Add(new Dae.CameraInstance { Target = new Dae.Camera { XFov = 45.0f, AspectRatio = 4.0f / 3.0f, ZNear = 1.0f, ZFar = 10000.0f } }); } } }