ParticleEffectReader.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  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. Texture2D texture = Texture2D.FromFile(graphicsDeviceService.GraphicsDevice, filePath);
  197. texture.Name = name;
  198. return new Texture2DRegion(texture, bounds);
  199. }
  200. catch
  201. {
  202. // Continue to next extension
  203. continue;
  204. }
  205. }
  206. }
  207. return null;
  208. }
  209. private ParticleReleaseParameters ReadParticleReleaseParameters(XmlReader reader)
  210. {
  211. ParticleReleaseParameters parameters = new ParticleReleaseParameters();
  212. using XmlReader subtree = reader.ReadSubtree();
  213. while (subtree.Read())
  214. {
  215. if (subtree.NodeType == XmlNodeType.Element)
  216. {
  217. switch (subtree.LocalName)
  218. {
  219. case nameof(ParticleReleaseParameters.Quantity):
  220. parameters.Quantity = ReadParticleInt32Parameter(subtree);
  221. break;
  222. case nameof(ParticleReleaseParameters.Speed):
  223. parameters.Speed = ReadParticleFloatParameter(subtree);
  224. break;
  225. case nameof(ParticleReleaseParameters.Color):
  226. parameters.Color = ReadParticleColorParameter(subtree);
  227. break;
  228. case nameof(ParticleReleaseParameters.Opacity):
  229. parameters.Opacity = ReadParticleFloatParameter(subtree);
  230. break;
  231. case nameof(ParticleReleaseParameters.Scale):
  232. parameters.Scale = ReadParticleVector2Parameter(subtree);
  233. break;
  234. case nameof(ParticleReleaseParameters.Rotation):
  235. parameters.Rotation = ReadParticleFloatParameter(subtree);
  236. break;
  237. case nameof(ParticleReleaseParameters.Mass):
  238. parameters.Mass = ReadParticleFloatParameter(subtree);
  239. break;
  240. }
  241. }
  242. }
  243. return parameters;
  244. }
  245. private ParticleInt32Parameter ReadParticleInt32Parameter(XmlReader reader)
  246. {
  247. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleInt32Parameter.Kind));
  248. if (kind == ParticleValueKind.Constant)
  249. {
  250. int value = reader.GetAttributeInt(nameof(ParticleInt32Parameter.Constant));
  251. return new ParticleInt32Parameter(value);
  252. }
  253. else if (kind == ParticleValueKind.Random)
  254. {
  255. int min = reader.GetAttributeInt(nameof(ParticleInt32Parameter.RandomMin));
  256. int max = reader.GetAttributeInt(nameof(ParticleInt32Parameter.RandomMax));
  257. return new ParticleInt32Parameter(min, max);
  258. }
  259. return new ParticleInt32Parameter(0);
  260. }
  261. private ParticleFloatParameter ReadParticleFloatParameter(XmlReader reader)
  262. {
  263. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleFloatParameter.Kind));
  264. if (kind == ParticleValueKind.Constant)
  265. {
  266. float value = reader.GetAttributeFloat(nameof(ParticleFloatParameter.Constant));
  267. return new ParticleFloatParameter(value);
  268. }
  269. else if (kind == ParticleValueKind.Random)
  270. {
  271. float min = reader.GetAttributeFloat(nameof(ParticleFloatParameter.RandomMin));
  272. float max = reader.GetAttributeFloat(nameof(ParticleFloatParameter.RandomMax));
  273. return new ParticleFloatParameter(min, max);
  274. }
  275. return new ParticleFloatParameter(0);
  276. }
  277. private ParticleVector2Parameter ReadParticleVector2Parameter(XmlReader reader)
  278. {
  279. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleVector2Parameter.Kind));
  280. if(kind == ParticleValueKind.Constant)
  281. {
  282. Vector2 value = reader.GetAttributeVector2(nameof(ParticleVector2Parameter.Constant));
  283. return new ParticleVector2Parameter(value);
  284. }
  285. else if(kind == ParticleValueKind.Random)
  286. {
  287. Vector2 min = reader.GetAttributeVector2(nameof(ParticleVector2Parameter.RandomMin));
  288. Vector2 max = reader.GetAttributeVector2(nameof(ParticleVector2Parameter.RandomMax));
  289. return new ParticleVector2Parameter(min, max);
  290. }
  291. return new ParticleVector2Parameter(Vector2.Zero);
  292. }
  293. private ParticleColorParameter ReadParticleColorParameter(XmlReader reader)
  294. {
  295. ParticleValueKind kind = reader.GetAttributeEnum<ParticleValueKind>(nameof(ParticleColorParameter.Kind));
  296. if (kind == ParticleValueKind.Constant)
  297. {
  298. Vector3 value = reader.GetAttributeVector3(nameof(ParticleColorParameter.Constant));
  299. return new ParticleColorParameter(value);
  300. }
  301. else if (kind == ParticleValueKind.Random)
  302. {
  303. Vector3 min = reader.GetAttributeVector3(nameof(ParticleColorParameter.RandomMin));
  304. Vector3 max = reader.GetAttributeVector3(nameof(ParticleColorParameter.RandomMax));
  305. return new ParticleColorParameter(min, max);
  306. }
  307. return new ParticleColorParameter(Vector3.Zero);
  308. }
  309. private Profile ReadProfile(XmlReader reader)
  310. {
  311. string type = reader.GetAttribute(nameof(Type));
  312. switch (type)
  313. {
  314. case nameof(BoxProfile):
  315. BoxProfile boxProfile = new BoxProfile();
  316. boxProfile.Width = reader.GetAttributeFloat(nameof(BoxProfile.Width));
  317. boxProfile.Height = reader.GetAttributeFloat(nameof(BoxProfile.Height));
  318. return boxProfile;
  319. case nameof(BoxFillProfile):
  320. BoxFillProfile boxFillProfile = new BoxFillProfile();
  321. boxFillProfile.Width = reader.GetAttributeFloat(nameof(BoxFillProfile.Width));
  322. boxFillProfile.Height = reader.GetAttributeFloat(nameof(BoxFillProfile.Height));
  323. return boxFillProfile;
  324. case nameof(BoxUniformProfile):
  325. BoxUniformProfile boxUniformProfile = new BoxUniformProfile();
  326. boxUniformProfile.Width = reader.GetAttributeFloat(nameof(BoxUniformProfile.Width));
  327. boxUniformProfile.Height = reader.GetAttributeFloat(nameof(BoxUniformProfile.Height));
  328. return boxUniformProfile;
  329. case nameof(CircleProfile):
  330. CircleProfile circleProfile = new CircleProfile();
  331. circleProfile.Radius = reader.GetAttributeFloat(nameof(CircleProfile.Radius));
  332. circleProfile.Radiate = reader.GetAttributeEnum<CircleRadiation>(nameof(CircleProfile.Radiate));
  333. return circleProfile;
  334. case nameof(LineProfile):
  335. LineProfile lineProfile = new LineProfile();
  336. lineProfile.Axis = reader.GetAttributeVector2(nameof(LineProfile.Axis));
  337. lineProfile.Length = reader.GetAttributeFloat(nameof(LineProfile.Length));
  338. return lineProfile;
  339. case nameof(PointProfile):
  340. return Profile.Point();
  341. case nameof(RingProfile):
  342. RingProfile ringProfile = new RingProfile();
  343. ringProfile.Radius = reader.GetAttributeFloat(nameof(RingProfile.Radius));
  344. ringProfile.Radiate = reader.GetAttributeEnum<CircleRadiation>(nameof(RingProfile.Radiate));
  345. return ringProfile;
  346. case nameof(SprayProfile):
  347. SprayProfile sprayProfile = new SprayProfile();
  348. sprayProfile.Direction = reader.GetAttributeVector2(nameof(SprayProfile.Direction));
  349. sprayProfile.Spread = reader.GetAttributeFloat(nameof(SprayProfile.Spread));
  350. return sprayProfile;
  351. default:
  352. return new PointProfile();
  353. }
  354. }
  355. private void ReadModifiers(XmlReader reader, List<Modifier> modifiers)
  356. {
  357. using XmlReader subtree = reader.ReadSubtree();
  358. while (subtree.Read())
  359. {
  360. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(Modifier))
  361. {
  362. Modifier modifier = ReadModifier(subtree);
  363. if (modifier != null)
  364. {
  365. modifiers.Add(modifier);
  366. }
  367. }
  368. }
  369. }
  370. private Modifier ReadModifier(XmlReader reader)
  371. {
  372. string type = reader.GetAttribute(nameof(Type));
  373. string name = reader.GetAttribute(nameof(Modifier.Name));
  374. float frequency = reader.GetAttributeFloat(nameof(Modifier.Frequency));
  375. Modifier modifier = type switch
  376. {
  377. nameof(AgeModifier) => ReadAgeModifier(reader),
  378. nameof(DragModifier) => ReadDragModifier(reader),
  379. nameof(LinearGravityModifier) => ReadLinearGravityModifier(reader),
  380. nameof(OpacityFastFadeModifier) => new OpacityFastFadeModifier(),
  381. nameof(RotationModifier) => ReadRotationModifier(reader),
  382. nameof(VelocityColorModifier) => ReadVelocityColorModifier(reader),
  383. nameof(VelocityModifier) => ReadVelocityModifier(reader),
  384. nameof(VortexModifier) => ReadVortexModifier(reader),
  385. nameof(CircleContainerModifier) => ReadCircleContainerModifier(reader),
  386. nameof(RectangleContainerModifier) => ReadRectangleContainerModifier(reader),
  387. nameof(RectangleLoopContainerModifier) => ReadRectangleLoopContainerModifier(reader),
  388. _ => null
  389. };
  390. if (modifier != null)
  391. {
  392. modifier.Name = name;
  393. modifier.Frequency = frequency;
  394. }
  395. return modifier;
  396. }
  397. private Modifier ReadAgeModifier(XmlReader reader)
  398. {
  399. AgeModifier modifier = new AgeModifier();
  400. using XmlReader subtree = reader.ReadSubtree();
  401. while (subtree.Read())
  402. {
  403. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(AgeModifier.Interpolators))
  404. {
  405. ReadInterpolators(subtree, modifier.Interpolators);
  406. }
  407. }
  408. return modifier;
  409. }
  410. private Modifier ReadDragModifier(XmlReader reader)
  411. {
  412. DragModifier modifier = new DragModifier();
  413. modifier.DragCoefficient = reader.GetAttributeFloat(nameof(DragModifier.DragCoefficient));
  414. modifier.Density = reader.GetAttributeFloat(nameof(DragModifier.Density));
  415. return modifier;
  416. }
  417. private LinearGravityModifier ReadLinearGravityModifier(XmlReader reader)
  418. {
  419. LinearGravityModifier modifier = new LinearGravityModifier();
  420. modifier.Direction = reader.GetAttributeVector2(nameof(LinearGravityModifier.Direction));
  421. modifier.Strength = reader.GetAttributeFloat(nameof(LinearGravityModifier.Strength));
  422. return modifier;
  423. }
  424. private Modifier ReadRotationModifier(XmlReader reader)
  425. {
  426. RotationModifier modifier = new RotationModifier();
  427. modifier.RotationRate = reader.GetAttributeFloat(nameof(RotationModifier.RotationRate));
  428. return modifier;
  429. }
  430. private Modifier ReadVelocityColorModifier(XmlReader reader)
  431. {
  432. VelocityColorModifier modifier = new VelocityColorModifier();
  433. modifier.StationaryColor = reader.GetAttributeVector3(nameof(VelocityColorModifier.StationaryColor));
  434. modifier.VelocityColor = reader.GetAttributeVector3(nameof(VelocityColorModifier.VelocityColor));
  435. modifier.VelocityThreshold = reader.GetAttributeFloat(nameof(VelocityColorModifier.VelocityThreshold));
  436. return modifier;
  437. }
  438. private Modifier ReadVelocityModifier(XmlReader reader)
  439. {
  440. VelocityModifier modifier = new VelocityModifier();
  441. modifier.VelocityThreshold = reader.GetAttributeFloat(nameof(VelocityModifier.VelocityThreshold));
  442. using XmlReader subtree = reader.ReadSubtree();
  443. while (subtree.Read())
  444. {
  445. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(VelocityModifier.Interpolators))
  446. {
  447. ReadInterpolators(subtree, modifier.Interpolators);
  448. }
  449. }
  450. return modifier;
  451. }
  452. private Modifier ReadVortexModifier(XmlReader reader)
  453. {
  454. VortexModifier modifier = new VortexModifier();
  455. modifier.Mass = reader.GetAttributeFloat(nameof(VortexModifier.Mass));
  456. modifier.MaxSpeed = reader.GetAttributeFloat(nameof(VortexModifier.MaxSpeed));
  457. return modifier;
  458. }
  459. private CircleContainerModifier ReadCircleContainerModifier(XmlReader reader)
  460. {
  461. CircleContainerModifier modifier = new CircleContainerModifier();
  462. modifier.Radius = reader.GetAttributeFloat(nameof(CircleContainerModifier.Radius));
  463. modifier.Inside = reader.GetAttributeBool(nameof(CircleContainerModifier.Inside));
  464. modifier.RestitutionCoefficient = reader.GetAttributeFloat(nameof(CircleContainerModifier.RestitutionCoefficient));
  465. return modifier;
  466. }
  467. private Modifier ReadRectangleContainerModifier(XmlReader reader)
  468. {
  469. RectangleContainerModifier modifier = new RectangleContainerModifier();
  470. modifier.Width = reader.GetAttributeInt(nameof(RectangleContainerModifier.Width));
  471. modifier.Height = reader.GetAttributeInt(nameof(RectangleContainerModifier.Height));
  472. modifier.RestitutionCoefficient = reader.GetAttributeFloat(nameof(RectangleContainerModifier.RestitutionCoefficient));
  473. return modifier;
  474. }
  475. private Modifier ReadRectangleLoopContainerModifier(XmlReader reader)
  476. {
  477. RectangleLoopContainerModifier modifier = new RectangleLoopContainerModifier();
  478. modifier.Width = reader.GetAttributeInt(nameof(RectangleLoopContainerModifier.Width));
  479. modifier.Height = reader.GetAttributeInt(nameof(RectangleLoopContainerModifier.Height));
  480. return modifier;
  481. }
  482. private void ReadInterpolators(XmlReader reader, List<Interpolator> interpolators)
  483. {
  484. using XmlReader subtree = reader.ReadSubtree();
  485. while (subtree.Read())
  486. {
  487. if (subtree.NodeType == XmlNodeType.Element && subtree.LocalName == nameof(Interpolator))
  488. {
  489. Interpolator interpolator = ReadInterpolator(subtree);
  490. if (interpolator != null)
  491. {
  492. interpolators.Add(interpolator);
  493. }
  494. }
  495. }
  496. }
  497. private Interpolator ReadInterpolator(XmlReader reader)
  498. {
  499. string type = reader.GetAttribute(nameof(Type));
  500. string name = reader.GetAttribute(nameof(Interpolator.Name));
  501. switch (type)
  502. {
  503. case nameof(ColorInterpolator):
  504. ColorInterpolator colorInterpolator = new ColorInterpolator();
  505. colorInterpolator.StartValue = reader.GetAttributeVector3(nameof(ColorInterpolator.StartValue));
  506. colorInterpolator.EndValue = reader.GetAttributeVector3(nameof(ColorInterpolator.EndValue));
  507. return colorInterpolator;
  508. case nameof(HueInterpolator):
  509. HueInterpolator hueInterpolator = new HueInterpolator();
  510. hueInterpolator.StartValue = reader.GetAttributeFloat(nameof(HueInterpolator.StartValue));
  511. hueInterpolator.EndValue = reader.GetAttributeFloat(nameof(HueInterpolator.EndValue));
  512. return hueInterpolator;
  513. case nameof(OpacityInterpolator):
  514. OpacityInterpolator opacityInterpolator = new OpacityInterpolator();
  515. opacityInterpolator.StartValue = reader.GetAttributeFloat(nameof(OpacityInterpolator.StartValue));
  516. opacityInterpolator.EndValue = reader.GetAttributeFloat(nameof(OpacityInterpolator.EndValue));
  517. return opacityInterpolator;
  518. case nameof(RotationInterpolator):
  519. RotationInterpolator rotationInterpolator = new RotationInterpolator();
  520. rotationInterpolator.StartValue = reader.GetAttributeFloat(nameof(RotationInterpolator.StartValue));
  521. rotationInterpolator.EndValue = reader.GetAttributeFloat(nameof(RotationInterpolator.EndValue));
  522. return rotationInterpolator;
  523. case nameof(ScaleInterpolator):
  524. ScaleInterpolator scaleInterpolator = new ScaleInterpolator();
  525. scaleInterpolator.StartValue = reader.GetAttributeVector2(nameof(ScaleInterpolator.StartValue));
  526. scaleInterpolator.EndValue = reader.GetAttributeVector2(nameof(ScaleInterpolator.EndValue));
  527. return scaleInterpolator;
  528. case nameof(VelocityInterpolator):
  529. VelocityInterpolator velocityInterpolator = new VelocityInterpolator();
  530. velocityInterpolator.StartValue = reader.GetAttributeVector2(nameof(VelocityInterpolator.StartValue));
  531. velocityInterpolator.EndValue = reader.GetAttributeVector2(nameof(VelocityInterpolator.EndValue));
  532. return velocityInterpolator;
  533. default:
  534. return null;
  535. }
  536. }
  537. /// <inheritdoc/>
  538. public void Dispose()
  539. {
  540. if (IsDisposed)
  541. {
  542. return;
  543. }
  544. _reader?.Dispose();
  545. IsDisposed = true;
  546. GC.SuppressFinalize(this);
  547. }
  548. }