ParticleEffectReader.cs 26 KB

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