ParticleEffectReader.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  1. // Copyright (c) Craftwork Games. All rights reserved.
  2. // Licensed under the MIT license.
  3. // See LICENSE file in the project root for full license information.
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Net;
  8. using System.Xml;
  9. using Microsoft.Xna.Framework;
  10. using Microsoft.Xna.Framework.Content;
  11. using Microsoft.Xna.Framework.Graphics;
  12. using MonoGame.Extended.Graphics;
  13. using MonoGame.Extended.Particles.Data;
  14. using MonoGame.Extended.Particles.Modifiers;
  15. using MonoGame.Extended.Particles.Modifiers.Containers;
  16. using MonoGame.Extended.Particles.Modifiers.Interpolators;
  17. using MonoGame.Extended.Particles.Profiles;
  18. using MonoGame.Extended.Serialization.Xml;
  19. namespace MonoGame.Extended.Particles;
  20. /// <summary>
  21. /// Represents a reader that deserializes a <see cref="ParticleEffect"/> from an XML configuration.
  22. /// </summary>
  23. [Obsolete("Use ParticleEffectSerializer.Deserialize. ParticleEffectReader will be removed in 6.0.0")]
  24. public sealed class ParticleEffectReader : IDisposable
  25. {
  26. private readonly XmlReader _reader;
  27. private readonly ContentManager _content;
  28. private readonly string _baseDirectory;
  29. /// <summary>
  30. /// Gets a value that indicates whether this <see cref="ParticleEffectReader"/> has been disposed of.
  31. /// </summary>
  32. public bool IsDisposed { get; private set; }
  33. /// <summary>
  34. /// Initializes a new instance of the <see cref="ParticleEffectReader"/> class that reads from a file.
  35. /// </summary>
  36. /// <param name="fileName">The file path to read the XMl from.</param>
  37. /// <param name="content">The <see cref="ContentManager"/> to use for loading textures.</param>
  38. /// <exception cref="ArgumentNullException"><paramref name="content"/> is <see langword="null"/></exception>
  39. /// <exception cref="ArgumentException"><paramref name="fileName"/> is <see langword="null"/> or empty.</exception>
  40. public ParticleEffectReader(string fileName, ContentManager content)
  41. {
  42. ArgumentNullException.ThrowIfNull(content);
  43. ArgumentException.ThrowIfNullOrEmpty(fileName);
  44. XmlReaderSettings settings = new XmlReaderSettings();
  45. settings.CloseInput = true;
  46. settings.IgnoreComments = true;
  47. settings.IgnoreWhitespace = true;
  48. _content = content;
  49. _reader = XmlReader.Create(fileName, settings);
  50. _baseDirectory = Path.GetDirectoryName(Path.GetFullPath(fileName));
  51. }
  52. /// <summary>
  53. /// Initializes a new instance of the <see cref="ParticleEffectReader"/> class that reads from a stream.
  54. /// </summary>
  55. /// <param name="stream">The stream to read from.</param>
  56. /// <param name="content">The <see cref="ContentManager"/> to use for loading textures.</param>
  57. /// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="content"/> is <see langword="null"/></exception>
  58. public ParticleEffectReader(Stream stream, ContentManager content)
  59. : this(stream, content, null)
  60. {
  61. }
  62. /// <summary>
  63. /// Initializes a new instance of the <see cref="ParticleEffectReader"/> class that reads from a stream.
  64. /// </summary>
  65. /// <param name="stream">The stream to read from.</param>
  66. /// <param name="content">The <see cref="ContentManager"/> to use for loading textures.</param>
  67. /// <param name="baseDirectory">The base directory to use for resolving relative texture paths. If null, uses the ContentManager's RootDirectory.</param>
  68. /// <exception cref="ArgumentNullException"><paramref name="stream"/> or <paramref name="content"/> is <see langword="null"/></exception>
  69. public ParticleEffectReader(Stream stream, ContentManager content, string baseDirectory)
  70. {
  71. ArgumentNullException.ThrowIfNull(stream);
  72. ArgumentNullException.ThrowIfNull(content);
  73. XmlReaderSettings settings = new XmlReaderSettings();
  74. settings.IgnoreComments = true;
  75. settings.IgnoreWhitespace = true;
  76. settings.CloseInput = false;
  77. _content = content;
  78. _reader = XmlReader.Create(stream, settings);
  79. _baseDirectory = baseDirectory ?? content.RootDirectory;
  80. }
  81. /// <summary/>
  82. ~ParticleEffectReader()
  83. {
  84. Dispose();
  85. }
  86. /// <summary>
  87. /// Reads a <see cref="ParticleEffect"/> from the XML input.
  88. /// </summary>
  89. /// <returns>The deserialized <see cref="ParticleEffect"/>.</returns>
  90. /// <exception cref="XmlException">The Xml format is invalid.</exception>
  91. public ParticleEffect ReadParticleEffect()
  92. {
  93. _reader.MoveToContent();
  94. if (_reader.NodeType != XmlNodeType.Element || _reader.LocalName != nameof(ParticleEffect))
  95. {
  96. throw new XmlException($"Expected {nameof(ParticleEffect)} root element");
  97. }
  98. string name = _reader.GetAttribute(nameof(ParticleEffect.Name)) ?? "Unnamed";
  99. ParticleEffect effect = new ParticleEffect(name);
  100. effect.Position = _reader.GetAttributeVector2(nameof(ParticleEffect.Position));
  101. effect.Rotation = _reader.GetAttributeFloat(nameof(ParticleEffect.Rotation));
  102. effect.Scale = _reader.GetAttributeVector2(nameof(ParticleEffect.Scale));
  103. effect.AutoTrigger = _reader.GetAttributeBool(nameof(ParticleEffect.AutoTrigger));
  104. effect.AutoTriggerFrequency = _reader.GetAttributeFloat(nameof(ParticleEffect.AutoTriggerFrequency));
  105. if (_reader.ReadToDescendant(nameof(ParticleEffect.Emitters)))
  106. {
  107. if (_reader.ReadToDescendant(nameof(ParticleEmitter)))
  108. {
  109. do
  110. {
  111. ParticleEmitter emitter = ReadParticleEmitter();
  112. effect.Emitters.Add(emitter);
  113. } while (_reader.ReadToNextSibling(nameof(ParticleEmitter)));
  114. }
  115. }
  116. return effect;
  117. }
  118. private ParticleEmitter ReadParticleEmitter()
  119. {
  120. int capacity = _reader.GetAttributeInt(nameof(ParticleEmitter.Capacity));
  121. ParticleEmitter emitter = new ParticleEmitter(capacity);
  122. emitter.Name = _reader.GetAttribute(nameof(ParticleEmitter.Name)) ?? nameof(ParticleEmitter.Name);
  123. emitter.LifeSpan = _reader.GetAttributeFloat(nameof(ParticleEmitter.LifeSpan));
  124. emitter.Offset = _reader.GetAttributeVector2(nameof(ParticleEmitter.Offset));
  125. emitter.LayerDepth = _reader.GetAttributeFloat(nameof(ParticleEmitter.LayerDepth));
  126. emitter.ReclaimFrequency = _reader.GetAttributeFloat(nameof(ParticleEmitter.ReclaimFrequency));
  127. string strategy = _reader.GetAttribute(nameof(ParticleEmitter.ModifierExecutionStrategy));
  128. if (strategy.Equals(nameof(ModifierExecutionStrategy.Serial)))
  129. {
  130. emitter.ModifierExecutionStrategy = ModifierExecutionStrategy.Serial;
  131. }
  132. else if (strategy.Equals(nameof(ModifierExecutionStrategy.Parallel)))
  133. {
  134. emitter.ModifierExecutionStrategy = ModifierExecutionStrategy.Parallel;
  135. }
  136. else
  137. {
  138. emitter.ModifierExecutionStrategy = ModifierExecutionStrategy.Serial;
  139. }
  140. emitter.RenderingOrder = _reader.GetAttributeEnum<ParticleRenderingOrder>(nameof(ParticleEmitter.RenderingOrder));
  141. using XmlReader subtree = _reader.ReadSubtree();
  142. while (subtree.Read())
  143. {
  144. if (subtree.NodeType == XmlNodeType.Element)
  145. {
  146. switch (subtree.LocalName)
  147. {
  148. case nameof(ParticleEmitter.TextureRegion):
  149. emitter.TextureRegion = ReadTexture2DRegion(subtree);
  150. break;
  151. case nameof(ParticleEmitter.Parameters):
  152. emitter.Parameters = ReadParticleReleaseParameters(subtree);
  153. break;
  154. case nameof(ParticleEmitter.Profile):
  155. emitter.Profile = ReadProfile(subtree);
  156. break;
  157. case nameof(ParticleEmitter.Modifiers):
  158. ReadModifiers(subtree, emitter.Modifiers);
  159. break;
  160. }
  161. }
  162. }
  163. return emitter;
  164. }
  165. private Texture2DRegion ReadTexture2DRegion(XmlReader reader)
  166. {
  167. string name = reader.GetAttribute(nameof(Texture2DRegion.Texture.Name));
  168. if (string.IsNullOrEmpty(name))
  169. {
  170. return null;
  171. }
  172. Rectangle bounds = _reader.GetAttributeRectangle(nameof(Texture2DRegion.Bounds));
  173. try
  174. {
  175. // Try loading directly though the content manager. This should work, but there is a bug
  176. // for relative paths which has been documented at
  177. // https://github.com/MonoGame/MonoGame/issues/8786
  178. // And a propsed fix at
  179. // https://github.com/MonoGame/MonoGame/pull/8787
  180. // But until that is merged and released, we'll attempt to load here, then fall back to direct load in
  181. // the catch
  182. Texture2D texture = _content.Load<Texture2D>(name);
  183. return new Texture2DRegion(texture, bounds);
  184. }
  185. catch (ContentLoadException)
  186. {
  187. return TryLoadTextureDirectly(name, bounds);
  188. }
  189. }
  190. private Texture2DRegion TryLoadTextureDirectly(string name, Rectangle bounds)
  191. {
  192. if(_content?.ServiceProvider == null)
  193. {
  194. return null;
  195. }
  196. IGraphicsDeviceService graphicsDeviceService = _content.ServiceProvider.GetService(typeof(IGraphicsDeviceService)) as IGraphicsDeviceService;
  197. if(graphicsDeviceService?.GraphicsDevice == null)
  198. {
  199. return null;
  200. }
  201. // Try common image extensions
  202. string filePath = Path.Combine(_baseDirectory, name);
  203. if (File.Exists(filePath))
  204. {
  205. try
  206. {
  207. #if KNI
  208. using FileStream stream = File.OpenRead(filePath);
  209. Texture2D texture = Texture2D.FromStream(graphicsDeviceService.GraphicsDevice, stream);
  210. #else
  211. Texture2D texture = Texture2D.FromFile(graphicsDeviceService.GraphicsDevice, filePath);
  212. #endif
  213. texture.Name = name;
  214. return new Texture2DRegion(texture, bounds);
  215. }
  216. catch
  217. {
  218. // TODO: 6.0.0
  219. // Since the file name is baked into the .ember file including
  220. // the extension, we no longer check extensions in a for each
  221. // loop. This means we can't just "continue" in the catch.
  222. // We'll need to throw an exception, but doing so would be
  223. // a breaking change, so we'll return null for now, document
  224. // it and update it for 6.0.0
  225. return null;
  226. }
  227. }
  228. return null;
  229. }
  230. private ParticleReleaseParameters ReadParticleReleaseParameters(XmlReader reader)
  231. {
  232. ParticleReleaseParameters parameters = new ParticleReleaseParameters();
  233. using XmlReader subtree = reader.ReadSubtree();
  234. while (subtree.Read())
  235. {
  236. if (subtree.NodeType == XmlNodeType.Element)
  237. {
  238. switch (subtree.LocalName)
  239. {
  240. case nameof(ParticleReleaseParameters.Quantity):
  241. parameters.Quantity = ReadParticleInt32Parameter(subtree);
  242. break;
  243. case nameof(ParticleReleaseParameters.Speed):
  244. parameters.Speed = ReadParticleFloatParameter(subtree);
  245. break;
  246. case nameof(ParticleReleaseParameters.Color):
  247. parameters.Color = ReadParticleColorParameter(subtree);
  248. break;
  249. case nameof(ParticleReleaseParameters.Opacity):
  250. parameters.Opacity = ReadParticleFloatParameter(subtree);
  251. break;
  252. case nameof(ParticleReleaseParameters.Scale):
  253. parameters.Scale = ReadParticleVector2Parameter(subtree);
  254. break;
  255. case nameof(ParticleReleaseParameters.Rotation):
  256. parameters.Rotation = ReadParticleFloatParameter(subtree);
  257. break;
  258. case nameof(ParticleReleaseParameters.Mass):
  259. parameters.Mass = ReadParticleFloatParameter(subtree);
  260. break;
  261. }
  262. }
  263. }
  264. return parameters;
  265. }
  266. private ParticleInt32Parameter ReadParticleInt32Parameter(XmlReader reader)
  267. {
  268. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleInt32Parameter.Kind));
  269. if (kind == ParticleValueKind.Constant)
  270. {
  271. int value = reader.GetAttributeInt(nameof(ParticleInt32Parameter.Constant));
  272. return new ParticleInt32Parameter(value);
  273. }
  274. else if (kind == ParticleValueKind.Random)
  275. {
  276. int min = reader.GetAttributeInt(nameof(ParticleInt32Parameter.RandomMin));
  277. int max = reader.GetAttributeInt(nameof(ParticleInt32Parameter.RandomMax));
  278. return new ParticleInt32Parameter(min, max);
  279. }
  280. return new ParticleInt32Parameter(0);
  281. }
  282. private ParticleFloatParameter ReadParticleFloatParameter(XmlReader reader)
  283. {
  284. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleFloatParameter.Kind));
  285. if (kind == ParticleValueKind.Constant)
  286. {
  287. float value = reader.GetAttributeFloat(nameof(ParticleFloatParameter.Constant));
  288. return new ParticleFloatParameter(value);
  289. }
  290. else if (kind == ParticleValueKind.Random)
  291. {
  292. float min = reader.GetAttributeFloat(nameof(ParticleFloatParameter.RandomMin));
  293. float max = reader.GetAttributeFloat(nameof(ParticleFloatParameter.RandomMax));
  294. return new ParticleFloatParameter(min, max);
  295. }
  296. return new ParticleFloatParameter(0);
  297. }
  298. private ParticleVector2Parameter ReadParticleVector2Parameter(XmlReader reader)
  299. {
  300. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleVector2Parameter.Kind));
  301. if(kind == ParticleValueKind.Constant)
  302. {
  303. Vector2 value = reader.GetAttributeVector2(nameof(ParticleVector2Parameter.Constant));
  304. return new ParticleVector2Parameter(value);
  305. }
  306. else if(kind == ParticleValueKind.Random)
  307. {
  308. Vector2 min = reader.GetAttributeVector2(nameof(ParticleVector2Parameter.RandomMin));
  309. Vector2 max = reader.GetAttributeVector2(nameof(ParticleVector2Parameter.RandomMax));
  310. return new ParticleVector2Parameter(min, max);
  311. }
  312. return new ParticleVector2Parameter(Vector2.Zero);
  313. }
  314. private ParticleColorParameter ReadParticleColorParameter(XmlReader reader)
  315. {
  316. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleColorParameter.Kind));
  317. if (kind == ParticleValueKind.Constant)
  318. {
  319. Vector3 value = reader.GetAttributeVector3(nameof(ParticleColorParameter.Constant));
  320. return new ParticleColorParameter(value);
  321. }
  322. else if (kind == ParticleValueKind.Random)
  323. {
  324. Vector3 min = reader.GetAttributeVector3(nameof(ParticleColorParameter.RandomMin));
  325. Vector3 max = reader.GetAttributeVector3(nameof(ParticleColorParameter.RandomMax));
  326. return new ParticleColorParameter(min, max);
  327. }
  328. return new ParticleColorParameter(Vector3.Zero);
  329. }
  330. private Profile ReadProfile(XmlReader reader)
  331. {
  332. string type = reader.GetAttribute(nameof(Type));
  333. switch (type)
  334. {
  335. case nameof(BoxProfile):
  336. BoxProfile boxProfile = new BoxProfile();
  337. boxProfile.Width = reader.GetAttributeFloat(nameof(BoxProfile.Width));
  338. boxProfile.Height = reader.GetAttributeFloat(nameof(BoxProfile.Height));
  339. return boxProfile;
  340. case nameof(BoxFillProfile):
  341. BoxFillProfile boxFillProfile = new BoxFillProfile();
  342. boxFillProfile.Width = reader.GetAttributeFloat(nameof(BoxFillProfile.Width));
  343. boxFillProfile.Height = reader.GetAttributeFloat(nameof(BoxFillProfile.Height));
  344. return boxFillProfile;
  345. case nameof(BoxUniformProfile):
  346. BoxUniformProfile boxUniformProfile = new BoxUniformProfile();
  347. boxUniformProfile.Width = reader.GetAttributeFloat(nameof(BoxUniformProfile.Width));
  348. boxUniformProfile.Height = reader.GetAttributeFloat(nameof(BoxUniformProfile.Height));
  349. return boxUniformProfile;
  350. case nameof(CircleProfile):
  351. CircleProfile circleProfile = new CircleProfile();
  352. circleProfile.Radius = reader.GetAttributeFloat(nameof(CircleProfile.Radius));
  353. circleProfile.Radiate = reader.GetAttributeEnum<CircleRadiation>(nameof(CircleProfile.Radiate));
  354. return circleProfile;
  355. case nameof(LineProfile):
  356. LineProfile lineProfile = new LineProfile();
  357. lineProfile.Axis = reader.GetAttributeVector2(nameof(LineProfile.Axis));
  358. lineProfile.Length = reader.GetAttributeFloat(nameof(LineProfile.Length));
  359. return lineProfile;
  360. case nameof(PointProfile):
  361. return Profile.Point();
  362. case nameof(RingProfile):
  363. RingProfile ringProfile = new RingProfile();
  364. ringProfile.Radius = reader.GetAttributeFloat(nameof(RingProfile.Radius));
  365. ringProfile.Radiate = reader.GetAttributeEnum<CircleRadiation>(nameof(RingProfile.Radiate));
  366. return ringProfile;
  367. case nameof(SprayProfile):
  368. SprayProfile sprayProfile = new SprayProfile();
  369. sprayProfile.Direction = reader.GetAttributeVector2(nameof(SprayProfile.Direction));
  370. sprayProfile.Spread = reader.GetAttributeFloat(nameof(SprayProfile.Spread));
  371. return sprayProfile;
  372. default:
  373. return new PointProfile();
  374. }
  375. }
  376. private void ReadModifiers(XmlReader reader, List<Modifier> modifiers)
  377. {
  378. using XmlReader subtree = reader.ReadSubtree();
  379. while (subtree.Read())
  380. {
  381. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(Modifier))
  382. {
  383. Modifier modifier = ReadModifier(subtree);
  384. if (modifier != null)
  385. {
  386. modifiers.Add(modifier);
  387. }
  388. }
  389. }
  390. }
  391. private Modifier ReadModifier(XmlReader reader)
  392. {
  393. string type = reader.GetAttribute(nameof(Type));
  394. string name = reader.GetAttribute(nameof(Modifier.Name));
  395. float frequency = reader.GetAttributeFloat(nameof(Modifier.Frequency));
  396. Modifier modifier = type switch
  397. {
  398. nameof(AgeModifier) => ReadAgeModifier(reader),
  399. nameof(DragModifier) => ReadDragModifier(reader),
  400. nameof(LinearGravityModifier) => ReadLinearGravityModifier(reader),
  401. nameof(OpacityFastFadeModifier) => new OpacityFastFadeModifier(),
  402. nameof(RotationModifier) => ReadRotationModifier(reader),
  403. nameof(VelocityColorModifier) => ReadVelocityColorModifier(reader),
  404. nameof(VelocityModifier) => ReadVelocityModifier(reader),
  405. nameof(VortexModifier) => ReadVortexModifier(reader),
  406. nameof(CircleContainerModifier) => ReadCircleContainerModifier(reader),
  407. nameof(RectangleContainerModifier) => ReadRectangleContainerModifier(reader),
  408. nameof(RectangleLoopContainerModifier) => ReadRectangleLoopContainerModifier(reader),
  409. _ => null
  410. };
  411. if (modifier != null)
  412. {
  413. modifier.Name = name;
  414. modifier.Frequency = frequency;
  415. }
  416. return modifier;
  417. }
  418. private Modifier ReadAgeModifier(XmlReader reader)
  419. {
  420. AgeModifier modifier = new AgeModifier();
  421. using XmlReader subtree = reader.ReadSubtree();
  422. while (subtree.Read())
  423. {
  424. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(AgeModifier.Interpolators))
  425. {
  426. ReadInterpolators(subtree, modifier.Interpolators);
  427. }
  428. }
  429. return modifier;
  430. }
  431. private Modifier ReadDragModifier(XmlReader reader)
  432. {
  433. DragModifier modifier = new DragModifier();
  434. modifier.DragCoefficient = reader.GetAttributeFloat(nameof(DragModifier.DragCoefficient));
  435. modifier.Density = reader.GetAttributeFloat(nameof(DragModifier.Density));
  436. return modifier;
  437. }
  438. private LinearGravityModifier ReadLinearGravityModifier(XmlReader reader)
  439. {
  440. LinearGravityModifier modifier = new LinearGravityModifier();
  441. modifier.Direction = reader.GetAttributeVector2(nameof(LinearGravityModifier.Direction));
  442. modifier.Strength = reader.GetAttributeFloat(nameof(LinearGravityModifier.Strength));
  443. return modifier;
  444. }
  445. private Modifier ReadRotationModifier(XmlReader reader)
  446. {
  447. RotationModifier modifier = new RotationModifier();
  448. modifier.RotationRate = reader.GetAttributeFloat(nameof(RotationModifier.RotationRate));
  449. return modifier;
  450. }
  451. private Modifier ReadVelocityColorModifier(XmlReader reader)
  452. {
  453. VelocityColorModifier modifier = new VelocityColorModifier();
  454. Vector3 stationaryColor = reader.GetAttributeVector3(nameof(VelocityColorModifier.StationaryColor));
  455. modifier.StationaryColor = new HslColor(stationaryColor.X, stationaryColor.Y, stationaryColor.Z);
  456. Vector3 velocityColor = reader.GetAttributeVector3(nameof(VelocityColorModifier.VelocityColor));
  457. modifier.VelocityColor = new HslColor(velocityColor.X, velocityColor.Y, velocityColor.Z);
  458. modifier.VelocityThreshold = reader.GetAttributeFloat(nameof(VelocityColorModifier.VelocityThreshold));
  459. return modifier;
  460. }
  461. private Modifier ReadVelocityModifier(XmlReader reader)
  462. {
  463. VelocityModifier modifier = new VelocityModifier();
  464. modifier.VelocityThreshold = reader.GetAttributeFloat(nameof(VelocityModifier.VelocityThreshold));
  465. using XmlReader subtree = reader.ReadSubtree();
  466. while (subtree.Read())
  467. {
  468. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(VelocityModifier.Interpolators))
  469. {
  470. ReadInterpolators(subtree, modifier.Interpolators);
  471. }
  472. }
  473. return modifier;
  474. }
  475. private Modifier ReadVortexModifier(XmlReader reader)
  476. {
  477. VortexModifier modifier = new VortexModifier();
  478. modifier.Position = reader.GetAttributeVector2(nameof(VortexModifier.Position));
  479. modifier.Strength = reader.GetAttributeFloat(nameof(VortexModifier.Strength));
  480. modifier.OuterRadius = reader.GetAttributeFloat(nameof(VortexModifier.OuterRadius));
  481. modifier.InnerRadius = reader.GetAttributeFloat(nameof(VortexModifier.InnerRadius));
  482. modifier.MaxVelocity = reader.GetAttributeFloat(nameof(VortexModifier.MaxVelocity));
  483. modifier.RotationAngle = reader.GetAttributeFloat(nameof(VortexModifier.RotationAngle));
  484. return modifier;
  485. }
  486. private CircleContainerModifier ReadCircleContainerModifier(XmlReader reader)
  487. {
  488. CircleContainerModifier modifier = new CircleContainerModifier();
  489. modifier.Radius = reader.GetAttributeFloat(nameof(CircleContainerModifier.Radius));
  490. modifier.Inside = reader.GetAttributeBool(nameof(CircleContainerModifier.Inside));
  491. modifier.RestitutionCoefficient = reader.GetAttributeFloat(nameof(CircleContainerModifier.RestitutionCoefficient));
  492. return modifier;
  493. }
  494. private Modifier ReadRectangleContainerModifier(XmlReader reader)
  495. {
  496. RectangleContainerModifier modifier = new RectangleContainerModifier();
  497. modifier.Width = reader.GetAttributeInt(nameof(RectangleContainerModifier.Width));
  498. modifier.Height = reader.GetAttributeInt(nameof(RectangleContainerModifier.Height));
  499. modifier.RestitutionCoefficient = reader.GetAttributeFloat(nameof(RectangleContainerModifier.RestitutionCoefficient));
  500. return modifier;
  501. }
  502. private Modifier ReadRectangleLoopContainerModifier(XmlReader reader)
  503. {
  504. RectangleLoopContainerModifier modifier = new RectangleLoopContainerModifier();
  505. modifier.Width = reader.GetAttributeInt(nameof(RectangleLoopContainerModifier.Width));
  506. modifier.Height = reader.GetAttributeInt(nameof(RectangleLoopContainerModifier.Height));
  507. return modifier;
  508. }
  509. private void ReadInterpolators(XmlReader reader, List<Interpolator> interpolators)
  510. {
  511. using XmlReader subtree = reader.ReadSubtree();
  512. while (subtree.Read())
  513. {
  514. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(Interpolator))
  515. {
  516. Interpolator interpolator = ReadInterpolator(subtree);
  517. if (interpolator != null)
  518. {
  519. interpolators.Add(interpolator);
  520. }
  521. }
  522. }
  523. }
  524. private Interpolator ReadInterpolator(XmlReader reader)
  525. {
  526. string type = reader.GetAttribute(nameof(Type));
  527. string name = reader.GetAttribute(nameof(Interpolator.Name));
  528. switch (type)
  529. {
  530. case nameof(ColorInterpolator):
  531. ColorInterpolator colorInterpolator = new ColorInterpolator();
  532. Vector3 startValue = reader.GetAttributeVector3(nameof(ColorInterpolator.StartValue));
  533. Vector3 endValue = reader.GetAttributeVector3(nameof(ColorInterpolator.EndValue));
  534. colorInterpolator.StartValue = new HslColor(startValue.X, startValue.Y, startValue.Z);
  535. colorInterpolator.EndValue = new HslColor(endValue.X, endValue.Y, endValue.Z);
  536. return colorInterpolator;
  537. case nameof(HueInterpolator):
  538. HueInterpolator hueInterpolator = new HueInterpolator();
  539. hueInterpolator.StartValue = reader.GetAttributeFloat(nameof(HueInterpolator.StartValue));
  540. hueInterpolator.EndValue = reader.GetAttributeFloat(nameof(HueInterpolator.EndValue));
  541. return hueInterpolator;
  542. case nameof(OpacityInterpolator):
  543. OpacityInterpolator opacityInterpolator = new OpacityInterpolator();
  544. opacityInterpolator.StartValue = reader.GetAttributeFloat(nameof(OpacityInterpolator.StartValue));
  545. opacityInterpolator.EndValue = reader.GetAttributeFloat(nameof(OpacityInterpolator.EndValue));
  546. return opacityInterpolator;
  547. case nameof(RotationInterpolator):
  548. RotationInterpolator rotationInterpolator = new RotationInterpolator();
  549. rotationInterpolator.StartValue = reader.GetAttributeFloat(nameof(RotationInterpolator.StartValue));
  550. rotationInterpolator.EndValue = reader.GetAttributeFloat(nameof(RotationInterpolator.EndValue));
  551. return rotationInterpolator;
  552. case nameof(ScaleInterpolator):
  553. ScaleInterpolator scaleInterpolator = new ScaleInterpolator();
  554. scaleInterpolator.StartValue = reader.GetAttributeVector2(nameof(ScaleInterpolator.StartValue));
  555. scaleInterpolator.EndValue = reader.GetAttributeVector2(nameof(ScaleInterpolator.EndValue));
  556. return scaleInterpolator;
  557. case nameof(VelocityInterpolator):
  558. VelocityInterpolator velocityInterpolator = new VelocityInterpolator();
  559. velocityInterpolator.StartValue = reader.GetAttributeVector2(nameof(VelocityInterpolator.StartValue));
  560. velocityInterpolator.EndValue = reader.GetAttributeVector2(nameof(VelocityInterpolator.EndValue));
  561. return velocityInterpolator;
  562. default:
  563. return null;
  564. }
  565. }
  566. /// <inheritdoc/>
  567. public void Dispose()
  568. {
  569. if (IsDisposed)
  570. {
  571. return;
  572. }
  573. _reader?.Dispose();
  574. IsDisposed = true;
  575. GC.SuppressFinalize(this);
  576. }
  577. }