using System; using System.Globalization; using System.Xml; using Oni.Imaging; using Oni.Metadata; namespace Oni.Xml { using Oni.Particles; internal class ParticleXmlImporter : ParticleXml { #region Private data private XmlReader xml; private Particle particle; #endregion public ParticleXmlImporter(XmlReader xml) { this.xml = xml; this.particle = new Particle(); } public static void Import(XmlReader xml, BinaryWriter writer) { int lengthPosition = writer.Position; writer.WriteUInt16(0); writer.WriteUInt16(18); var reader = new ParticleXmlImporter(xml); reader.Read(); reader.particle.Write(writer); int length = writer.Position - lengthPosition; writer.PushPosition(lengthPosition); writer.WriteUInt16(length); writer.PopPosition(); } public void Read() { ReadOptions(); ReadProperties(); ReadAppearance(); ReadAttractor(); ReadVariables(); ReadEmitters(); ReadEvents(); } private void ReadOptions() { if (!xml.IsStartElement("Options")) return; xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "DisableDetailLevel": particle.DisableDetailLevel = MetaEnum.Parse(xml.ReadElementContentAsString()); continue; case "Lifetime": particle.Lifetime = ReadValueFloat(); continue; case "CollisionRadius": particle.CollisionRadius = ReadValueFloat(); continue; case "FlyBySoundName": particle.FlyBySoundName = xml.ReadElementContentAsString(); continue; case "AIAlertRadius": particle.AIAlertRadius = xml.ReadElementContentAsFloat(); continue; case "AIDodgeRadius": particle.AIDodgeRadius = xml.ReadElementContentAsFloat(); continue; default: if (ReadFlag1() || ReadFlag2()) continue; break; } throw new FormatException(string.Format("Unknown option {0}", xml.LocalName)); } xml.ReadEndElement(); } private void ReadProperties() { if (!xml.IsStartElement("Properties")) return; xml.ReadStartElement(); while (xml.IsStartElement()) { if (ReadFlag1()) continue; throw new FormatException(string.Format("Unknown property {0}", xml.LocalName)); } xml.ReadEndElement(); } private void ReadAppearance() { if (!xml.IsStartElement("Appearance")) return; Appearance appearance = particle.Appearance; xml.ReadStartElement(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "DisplayType": string text = xml.ReadElementContentAsString(); switch (text) { case "Geometry": particle.Flags1 |= ParticleFlags1.Geometry; break; case "Vector": particle.Flags2 |= ParticleFlags2.Vector; break; case "Decal": particle.Flags2 |= ParticleFlags2.Decal; break; default: particle.SpriteType = MetaEnum.Parse(text); break; } continue; case "TexGeom": appearance.TextureName = xml.ReadElementContentAsString(); continue; case "Scale": appearance.Scale = ReadValueFloat(); continue; case "YScale": appearance.YScale = ReadValueFloat(); continue; case "Rotation": appearance.Rotation = ReadValueFloat(); continue; case "Alpha": appearance.Alpha = ReadValueFloat(); continue; case "XOffset": appearance.XOffset = ReadValueFloat(); continue; case "XShorten": appearance.XShorten = ReadValueFloat(); continue; case "Tint": appearance.Tint = ReadValueColor(); continue; case "EdgeFadeMin": appearance.EdgeFadeMin = ReadValueFloat(); continue; case "EdgeFadeMax": appearance.EdgeFadeMax = ReadValueFloat(); continue; case "MaxContrailDistance": appearance.MaxContrail = ReadValueFloat(); continue; case "LensFlareDistance": appearance.LensFlareDistance = ReadValueFloat(); continue; case "LensFlareFadeInFrames": appearance.LensFlareFadeInFrames = xml.ReadElementContentAsInt(); continue; case "LensFlareFadeOutFrames": appearance.LensFlareFadeOutFrames = xml.ReadElementContentAsInt(); continue; case "MaxDecals": appearance.MaxDecals = xml.ReadElementContentAsInt(); continue; case "DecalFadeFrames": appearance.DecalFadeFrames = xml.ReadElementContentAsInt(); continue; case "DecalWrapAngle": appearance.DecalWrapAngle = ReadValueFloat(); continue; default: if (ReadFlag1() || ReadFlag2()) continue; break; } throw new FormatException(string.Format("Unknown appearance property {0}", xml.LocalName)); } xml.ReadEndElement(); } private void ReadAttractor() { if (!xml.IsStartElement("Attractor")) return; xml.ReadStartElement(); Attractor attractor = particle.Attractor; while (xml.IsStartElement()) { switch (xml.LocalName) { case "Target": attractor.Target = (AttractorTarget)Enum.Parse(typeof(AttractorTarget), xml.ReadElementContentAsString()); continue; case "Selector": attractor.Selector = (AttractorSelector)Enum.Parse(typeof(AttractorSelector), xml.ReadElementContentAsString()); continue; case "Class": attractor.ClassName = xml.ReadElementContentAsString(); continue; case "MaxDistance": attractor.MaxDistance = ReadValueFloat(); continue; case "MaxAngle": attractor.MaxAngle = ReadValueFloat(); continue; case "AngleSelectMax": attractor.AngleSelectMax = ReadValueFloat(); continue; case "AngleSelectMin": attractor.AngleSelectMin = ReadValueFloat(); continue; case "AngleSelectWeight": attractor.AngleSelectWeight = ReadValueFloat(); continue; } throw new FormatException(string.Format("Unknown attractor property {0}", xml.LocalName)); } xml.ReadEndElement(); } private void ReadVariables() { if (!xml.IsStartElement("Variables")) return; if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); while (xml.IsStartElement()) particle.Variables.Add(ReadVariable()); xml.ReadEndElement(); } } private Variable ReadVariable() { string typeName = xml.LocalName; string name = xml.GetAttribute("Name"); switch (typeName) { case "Float": return new Variable(name, StorageType.Float, ReadValueFloat()); case "Color": return new Variable(name, StorageType.Color, ReadValueColor()); case "PingPongState": return new Variable(name, StorageType.PingPongState, ReadValueInt()); default: throw new XmlException(string.Format("Unknown variable type '{0}'", typeName)); } } private void ReadEmitters() { if (!xml.IsStartElement("Emitters")) return; if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); while (xml.IsStartElement()) particle.Emitters.Add(ReadEmitter()); xml.ReadEndElement(); } } private Emitter ReadEmitter() { xml.ReadStartElement(); Emitter emitter = new Emitter(); while (xml.IsStartElement()) { switch (xml.LocalName) { case "Class": emitter.ParticleClass = xml.ReadElementContentAsString(); continue; case "Flags": emitter.Flags = MetaEnum.Parse(xml.ReadElementContentAsString()); continue; case "TurnOffTreshold": emitter.TurnOffTreshold = xml.ReadElementContentAsInt(); continue; case "Probability": emitter.Probability = (int)(xml.ReadElementContentAsFloat() * 65535.0f); continue; case "Copies": emitter.Copies = xml.ReadElementContentAsFloat(); continue; case "LinkTo": string text = xml.ReadElementContentAsString(); if (!string.IsNullOrEmpty(text)) { if (text == "this") emitter.LinkTo = 1; else if (text == "link") emitter.LinkTo = 10; else emitter.LinkTo = int.Parse(text, CultureInfo.InvariantCulture) + 2; } continue; case "Rate": xml.ReadStartElement(); ReadEmitterRate(emitter); xml.ReadEndElement(); continue; case "Position": xml.ReadStartElement(); ReadEmitterPosition(emitter); xml.ReadEndElement(); continue; case "Speed": xml.ReadStartElement(); ReadEmitterSpeed(emitter); xml.ReadEndElement(); continue; case "Direction": xml.ReadStartElement(); ReadEmitterDirection(emitter); xml.ReadEndElement(); continue; case "Orientation": emitter.OrientationDir = MetaEnum.Parse(xml.ReadElementContentAsString()); continue; case "OrientationUp": emitter.OrientationUp = MetaEnum.Parse(xml.ReadElementContentAsString()); continue; } throw new FormatException(string.Format("Unknown emitter property '{0}'", xml.LocalName)); } xml.ReadEndElement(); return emitter; } private void ReadEmitterRate(Emitter emitter) { emitter.Rate = MetaEnum.Parse(xml.LocalName); if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); switch (emitter.Rate) { case EmitterRate.Continous: emitter.Parameters[0] = ReadValueFloat("Interval"); break; case EmitterRate.Random: emitter.Parameters[0] = ReadValueFloat("MinInterval"); emitter.Parameters[1] = ReadValueFloat("MaxInterval"); break; case EmitterRate.Distance: emitter.Parameters[0] = ReadValueFloat("Distance"); break; case EmitterRate.Attractor: emitter.Parameters[0] = ReadValueFloat("RechargeTime"); emitter.Parameters[1] = ReadValueFloat("CheckInterval"); break; } xml.ReadEndElement(); } } private void ReadEmitterPosition(Emitter emitter) { emitter.Position = MetaEnum.Parse(xml.LocalName); if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); switch (emitter.Position) { case EmitterPosition.Line: emitter.Parameters[2] = ReadValueFloat("Radius"); break; case EmitterPosition.Circle: case EmitterPosition.Sphere: emitter.Parameters[2] = ReadValueFloat("InnerRadius"); emitter.Parameters[3] = ReadValueFloat("OuterRadius"); break; case EmitterPosition.Offset: emitter.Parameters[2] = ReadValueFloat("X"); emitter.Parameters[3] = ReadValueFloat("Y"); emitter.Parameters[4] = ReadValueFloat("Z"); break; case EmitterPosition.Cylinder: emitter.Parameters[2] = ReadValueFloat("Height"); emitter.Parameters[3] = ReadValueFloat("InnerRadius"); emitter.Parameters[4] = ReadValueFloat("OuterRadius"); break; case EmitterPosition.BodySurface: case EmitterPosition.BodyBones: emitter.Parameters[2] = ReadValueFloat("OffsetRadius"); break; } xml.ReadEndElement(); } } private void ReadEmitterDirection(Emitter emitter) { emitter.Direction = MetaEnum.Parse(xml.LocalName); if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); switch (emitter.Direction) { case EmitterDirection.Cone: emitter.Parameters[5] = ReadValueFloat("Angle"); emitter.Parameters[6] = ReadValueFloat("CenterBias"); break; case EmitterDirection.Ring: emitter.Parameters[5] = ReadValueFloat("Angle"); emitter.Parameters[6] = ReadValueFloat("Offset"); break; case EmitterDirection.Offset: emitter.Parameters[5] = ReadValueFloat("X"); emitter.Parameters[6] = ReadValueFloat("Y"); emitter.Parameters[7] = ReadValueFloat("Z"); break; case EmitterDirection.Inaccurate: emitter.Parameters[5] = ReadValueFloat("BaseAngle"); emitter.Parameters[6] = ReadValueFloat("Inaccuracy"); emitter.Parameters[7] = ReadValueFloat("CenterBias"); break; } xml.ReadEndElement(); } } private void ReadEmitterSpeed(Emitter emitter) { emitter.Speed = MetaEnum.Parse(xml.LocalName); if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); switch (emitter.Speed) { case EmitterSpeed.Uniform: emitter.Parameters[8] = ReadValueFloat("Speed"); break; case EmitterSpeed.Stratified: emitter.Parameters[8] = ReadValueFloat("Speed1"); emitter.Parameters[9] = ReadValueFloat("Speed2"); break; } xml.ReadEndElement(); } } private void ReadEvents() { if (!xml.IsStartElement("Events")) return; if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); while (xml.IsStartElement()) ReadEvent(); xml.ReadEndElement(); } } private void ReadEvent() { Event e = new Event((EventType)Enum.Parse(typeof(EventType), xml.LocalName)); if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); while (xml.IsStartElement()) e.Actions.Add(ReadEventAction()); xml.ReadEndElement(); } particle.Events.Add(e); } private EventAction ReadEventAction() { EventAction action = new EventAction((EventActionType)Enum.Parse(typeof(EventActionType), xml.LocalName)); EventActionInfo info = eventActionInfoTable[(int)action.Type]; if (xml.IsEmptyElement) { xml.ReadStartElement(); } else { xml.ReadStartElement(); for (int i = 0; xml.IsStartElement(); i++) { if (i < info.OutCount) { action.Variables.Add(new VariableReference(xml.ReadElementContentAsString())); continue; } if (i >= info.Parameters.Length) throw new XmlException(string.Format("Too many arguments for action '{0}'", action.Type)); switch (info.Parameters[i].Type) { case StorageType.Float: case StorageType.BlastFalloff: action.Parameters.Add(ReadValueFloat()); continue; case StorageType.Color: action.Parameters.Add(ReadValueColor()); continue; case StorageType.ActionIndex: case StorageType.Emitter: case StorageType.CoordFrame: case StorageType.CollisionOrient: case StorageType.Boolean: case StorageType.PingPongState: case StorageType.ImpactModifier: case StorageType.DamageType: case StorageType.Direction: action.Parameters.Add(ReadValueInt()); continue; case StorageType.ImpulseSoundName: case StorageType.AmbientSoundName: case StorageType.ImpactName: action.Parameters.Add(ReadValueInstance()); continue; } } xml.ReadEndElement(); } return action; } private Value ReadValueInstance() { return new Value(ValueType.InstanceName, xml.ReadElementContentAsString()); } private Value ReadValueInt() { Value value = null; xml.ReadStartElement(); string text = xml.ReadElementContentAsString(); int i; if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out i)) value = new Value(i); else value = new Value(ValueType.Variable, text.Trim()); xml.ReadEndElement(); return value; } private Value ReadValueFloat(string name) { if (xml.LocalName != name) throw new XmlException(string.Format(CultureInfo.CurrentCulture, "Unexpected '{0}' element found at line {1}", xml.LocalName, 0)); return ReadValueFloat(); } private Value ReadValueFloat() { if (xml.IsEmptyElement) { xml.Read(); return new Value(0.0f); } Value value = null; xml.ReadStartElement(); if (xml.NodeType == XmlNodeType.Text) { string text = xml.ReadElementContentAsString(); float f; if (float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out f)) value = new Value(f); else value = new Value(ValueType.Variable, text.Trim()); } else if (xml.NodeType == XmlNodeType.Element) { string name = xml.LocalName; if (name == "Random") { float min = float.Parse(xml.GetAttribute("Min"), CultureInfo.InvariantCulture); float max = float.Parse(xml.GetAttribute("Max"), CultureInfo.InvariantCulture); value = new Value(ValueType.FloatRandom, min, max); } else if (name == "BellCurve") { float mean = float.Parse(xml.GetAttribute("Mean"), CultureInfo.InvariantCulture); float stddev = float.Parse(xml.GetAttribute("StdDev"), CultureInfo.InvariantCulture); value = new Value(ValueType.FloatBellCurve, mean, stddev); } else { throw new XmlException(string.Format(CultureInfo.CurrentCulture, "Unknown value type '{0}'", name)); } xml.ReadStartElement(); } xml.ReadEndElement(); return value; } private Value ReadValueColor() { Value value = null; xml.ReadStartElement(); if (xml.NodeType == XmlNodeType.Text) { string text = xml.ReadElementContentAsString(); Color c; if (Color.TryParse(text, out c)) value = new Value(c); else value = new Value(ValueType.Variable, text.Trim()); } else if (xml.NodeType == XmlNodeType.Element) { string name = xml.LocalName; if (name == "Random") { Color min = Color.Parse(xml.GetAttribute("Min")); Color max = Color.Parse(xml.GetAttribute("Max")); value = new Value(ValueType.ColorRandom, min, max); } else if (name == "BellCurve") { Color mean = Color.Parse(xml.GetAttribute("Mean")); Color stddev = Color.Parse(xml.GetAttribute("StdDev")); value = new Value(ValueType.ColorBellCurve, mean, stddev); } else { throw new XmlException(string.Format(CultureInfo.CurrentCulture, "Unknown value type '{0}'", name)); } xml.ReadStartElement(); } xml.ReadEndElement(); return value; } private bool ReadFlag1() { ParticleFlags1 flag; try { flag = (ParticleFlags1)Enum.Parse(typeof(ParticleFlags1), xml.LocalName); } catch { return false; } if (ReadFlagValue()) particle.Flags1 |= flag; return true; } private bool ReadFlag2() { ParticleFlags2 flag; try { flag = (ParticleFlags2)Enum.Parse(typeof(ParticleFlags2), xml.LocalName); } catch { return false; } if (ReadFlagValue()) particle.Flags2 |= flag; return true; } private bool ReadFlagValue() { string text = xml.ReadElementContentAsString(); switch (text) { case "false": return false; case "true": return true; default: throw new FormatException(string.Format(CultureInfo.CurrentCulture, "Unknown value '{0}'", text)); } } } }