Particles data driven (#388)

* yay, a start on data driven particles

* added data driven support for all particle profiles

* pretty much finished all of the particle serialization code

* resolved an inconsistency with the interpolators
This commit is contained in:
Dylan Wilson
2017-05-09 23:48:29 +10:00
committed by GitHub
parent 4bb614925c
commit 799b1edb06
38 changed files with 641 additions and 74 deletions
@@ -217,5 +217,47 @@ namespace MonoGame.Extended.Particles
c1.S + t*(c2.S - c1.S),
c1.L + t*(c2.L - c2.L));
}
public static HslColor FromRgb(Color color)
{
// derived from http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
var r = color.R / 255f;
var g = color.G / 255f;
var b = color.B / 255f;
var h = 0f; // default to black
var s = 0f;
var l = 0f;
var v = Math.Max(r, g);
v = Math.Max(v, b);
var m = Math.Min(r, g);
m = Math.Min(m, b);
l = (m + v) / 2.0f;
if (l <= 0.0)
return new HslColor(h, s, l);
var vm = v - m;
s = vm;
if (s > 0.0)
s /= l <= 0.5f ? v + m : 2.0f - v - m;
else
return new HslColor(h, s, l);
var r2 = (v - r) / vm;
var g2 = (v - g) / vm;
var b2 = (v - b) / vm;
if (Math.Abs(r - v) < float.Epsilon)
h = Math.Abs(g - m) < float.Epsilon ? 5.0f + b2 : 1.0f - g2;
else if (Math.Abs(g - v) < float.Epsilon)
h = Math.Abs(b - m) < float.Epsilon ? 1.0f + r2 : 3.0f - b2;
else
h = Math.Abs(r - m) < float.Epsilon ? 3.0f + g2 : 5.0f - r2;
h /= 6.0f;
return new HslColor(h, s, l);
}
}
}
@@ -1,23 +1,40 @@
namespace MonoGame.Extended.Particles.Modifiers.Interpolators
using System;
namespace MonoGame.Extended.Particles.Modifiers.Interpolators
{
/// <summary>
/// Defines a modifier which interpolates the color of a particle over the course of its lifetime.
/// </summary>
public sealed class ColorInterpolator : IInterpolator
public sealed class ColorInterpolator : IInterpolator<HslColor>
{
/// <summary>
/// Gets or sets the initial color of particles when they are released.
/// </summary>
public HslColor InitialColor { get; set; }
public HslColor StartValue { get; set; }
/// <summary>
/// Gets or sets the final color of particles when they are retired.
/// </summary>
public HslColor FinalColor { get; set; }
public HslColor EndValue { get; set; }
[Obsolete("Use StartValue instead")]
public HslColor InitialColor
{
get { return StartValue; }
set { StartValue = value; }
}
[Obsolete("Use EndValue instead")]
public HslColor FinalColor
{
get { return EndValue; }
set { EndValue = value; }
}
public unsafe void Update(float amount, Particle* particle)
{
particle->Color = HslColor.Lerp(InitialColor, FinalColor, amount);
particle->Color = HslColor.Lerp(StartValue, EndValue, amount);
}
}
}
@@ -3,8 +3,8 @@
public class HueInterpolator : IInterpolator
{
private float _delta;
public float StartValue { get; set; }
public float StartValue { get; set; }
public float EndValue
{
get { return _delta + StartValue; }
@@ -4,4 +4,10 @@
{
unsafe void Update(float amount, Particle* particle);
}
public interface IInterpolator<T> : IInterpolator
{
T StartValue { get; set; }
T EndValue { get; set; }
}
}
@@ -3,6 +3,7 @@
public class OpacityInterpolator : IInterpolator
{
private float _delta;
public float StartValue { get; set; }
public float EndValue
@@ -3,6 +3,7 @@
public class RotationInterpolator : IInterpolator
{
private float _delta;
public float StartValue { get; set; }
public float EndValue
@@ -5,6 +5,7 @@ namespace MonoGame.Extended.Particles.Modifiers.Interpolators
public class ScaleInterpolator : IInterpolator
{
private Vector2 _delta;
public Vector2 StartValue { get; set; }
public Vector2 EndValue
@@ -6,6 +6,7 @@ namespace MonoGame.Extended.Particles.Modifiers
{
// Note: not the real-life one
private const float _gravConst = 100000f;
public Vector2 Position { get; set; }
public float Mass { get; set; }
public float MaxSpeed { get; set; }
@@ -82,11 +82,21 @@
<Compile Include="Profiles\RingProfile.cs" />
<Compile Include="Profiles\SprayProfile.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Serialization\HslColorJsonConverter.cs" />
<Compile Include="Serialization\InterpolatorJsonConverter.cs" />
<Compile Include="Serialization\ModifierExecutionStrategyJsonConverter.cs" />
<Compile Include="Serialization\ModifierJsonConverter.cs" />
<Compile Include="Serialization\ParticleJsonSerializer.cs" />
<Compile Include="Serialization\ProfileJsonConverter.cs" />
<Compile Include="Serialization\TimeSpanJsonConverter.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="MonoGame.Framework, Version=3.6.0.1625, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
@@ -1,5 +1,10 @@
using System.Linq;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.Particles.Serialization;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.Particles
{
@@ -26,6 +31,25 @@ namespace MonoGame.Extended.Particles
}
}
public static ParticleEffect FromFile(ITextureRegionService textureRegionService, string path)
{
using (var stream = TitleContainer.OpenStream(path))
{
return FromStream(textureRegionService, stream);
}
}
public static ParticleEffect FromStream(ITextureRegionService textureRegionService, Stream stream)
{
var skinSerializer = new ParticleJsonSerializer(textureRegionService);
using (var streamReader = new StreamReader(stream))
using (var jsonReader = new JsonTextReader(streamReader))
{
return skinSerializer.Deserialize<ParticleEffect>(jsonReader);
}
}
public void Update(float elapsedSeconds)
{
foreach (var e in Emitters)
@@ -15,8 +15,7 @@ namespace MonoGame.Extended.Particles
private float _totalSeconds;
public ParticleEmitter(TextureRegion2D textureRegion, int capacity, TimeSpan term, Profile profile,
bool autoTrigger = true)
public ParticleEmitter(TextureRegion2D textureRegion, int capacity, TimeSpan term, Profile profile, bool autoTrigger = true)
{
if (profile == null)
throw new ArgumentNullException(nameof(profile));
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using MonoGame.Extended.Particles.Modifiers;
namespace MonoGame.Extended.Particles
@@ -7,11 +8,10 @@ namespace MonoGame.Extended.Particles
public abstract class ParticleModifierExecutionStrategy
{
public static ParticleModifierExecutionStrategy Serial = new SerialModifierExecutionStrategy();
public static ParticleModifierExecutionStrategy Parallel = new ParallelModifierExecutionStrategy();
public static readonly ParticleModifierExecutionStrategy Serial = new SerialModifierExecutionStrategy();
public static readonly ParticleModifierExecutionStrategy Parallel = new ParallelModifierExecutionStrategy();
internal abstract void ExecuteModifiers(IEnumerable<IModifier> modifiers, float elapsedSeconds,
ParticleBuffer.ParticleIterator iterator);
internal abstract void ExecuteModifiers(IEnumerable<IModifier> modifiers, float elapsedSeconds, ParticleBuffer.ParticleIterator iterator);
internal class SerialModifierExecutionStrategy : ParticleModifierExecutionStrategy
{
@@ -31,5 +31,16 @@ namespace MonoGame.Extended.Particles
TPL.Parallel.ForEach(modifiers, modifier => modifier.Update(elapsedSeconds, iterator.Reset()));
}
}
public static ParticleModifierExecutionStrategy Parse(string value)
{
if (string.Equals(nameof(Parallel), value, StringComparison.OrdinalIgnoreCase))
return Parallel;
if (string.Equals(nameof(Serial), value, StringComparison.OrdinalIgnoreCase))
return Serial;
throw new InvalidOperationException($"Unknown particle modifier execution strategy '{value}'");
}
}
}
@@ -2,7 +2,7 @@
namespace MonoGame.Extended.Particles.Profiles
{
public abstract class Profile //: ICloneable
public abstract class Profile
{
public enum CircleRadiation
{
@@ -0,0 +1,29 @@
using System;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
namespace MonoGame.Extended.Particles.Serialization
{
public class HslColorJsonConverter : JsonConverter
{
private readonly ColorJsonConverter _colorConverter = new ColorJsonConverter();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var color = ((HslColor) value).ToRgb();
_colorConverter.WriteJson(writer, color, serializer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var color = (Color)_colorConverter.ReadJson(reader, objectType, existingValue, serializer);
return HslColor.FromRgb(color);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HslColor);
}
}
}
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MonoGame.Extended.Particles.Modifiers.Interpolators;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.Particles.Serialization
{
public class InterpolatorJsonConverter : BaseTypeJsonConverter<IInterpolator>
{
public InterpolatorJsonConverter()
: base(GetSupportedTypes(), "Interpolator")
{
}
private static IEnumerable<TypeInfo> GetSupportedTypes()
{
return typeof(IInterpolator)
.GetTypeInfo()
.Assembly
.DefinedTypes
.Where(type => typeof(IInterpolator).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract);
}
}
}
@@ -0,0 +1,24 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MonoGame.Extended.Particles.Serialization
{
public class ModifierExecutionStrategyJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = JToken.Load(reader).ToObject<string>();
return ParticleModifierExecutionStrategy.Parse(value);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ParticleModifierExecutionStrategy);
}
}
}
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MonoGame.Extended.Particles.Modifiers;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.Particles.Serialization
{
public class ModifierJsonConverter : BaseTypeJsonConverter<IModifier>
{
public ModifierJsonConverter()
: base(GetSupportedTypes(), "Modifier")
{
}
private static IEnumerable<TypeInfo> GetSupportedTypes()
{
return typeof(IModifier)
.GetTypeInfo()
.Assembly
.DefinedTypes
.Where(type => typeof(IModifier).GetTypeInfo().IsAssignableFrom(type) && !type.IsAbstract);
}
}
}
@@ -0,0 +1,27 @@
using MonoGame.Extended.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MonoGame.Extended.Particles.Serialization
{
public sealed class ParticleJsonSerializer : JsonSerializer
{
public ParticleJsonSerializer(ITextureRegionService textureRegionService)
{
Converters.Add(new Vector2JsonConverter());
Converters.Add(new Size2JsonConverter());
Converters.Add(new ColorJsonConverter());
Converters.Add(new TextureRegion2DJsonConverter(textureRegionService));
Converters.Add(new ProfileJsonConverter());
Converters.Add(new ModifierJsonConverter());
Converters.Add(new InterpolatorJsonConverter());
Converters.Add(new TimeSpanJsonConverter());
Converters.Add(new RangeJsonConverter<int>());
Converters.Add(new RangeJsonConverter<float>());
Converters.Add(new HslColorJsonConverter());
Converters.Add(new ModifierExecutionStrategyJsonConverter());
ContractResolver = new ShortNameJsonContractResolver();
Formatting = Formatting.Indented;
}
}
}
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MonoGame.Extended.Particles.Profiles;
using MonoGame.Extended.Serialization;
namespace MonoGame.Extended.Particles.Serialization
{
public class ProfileJsonConverter : BaseTypeJsonConverter<Profile>
{
public ProfileJsonConverter()
: base(GetSupportedTypes(), nameof(Profile))
{
}
private static IEnumerable<TypeInfo> GetSupportedTypes()
{
return typeof(Profile)
.GetTypeInfo()
.Assembly
.DefinedTypes
.Where(type => type.IsSubclassOf(typeof(Profile)) && !type.IsAbstract);
}
}
}
@@ -0,0 +1,28 @@
using System;
using Newtonsoft.Json;
namespace MonoGame.Extended.Particles.Serialization
{
public class TimeSpanJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.ValueType == typeof(double))
{
var seconds = (double) reader.Value;
return TimeSpan.FromSeconds(seconds);
}
return TimeSpan.Zero;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TimeSpan);
}
}
}
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MonoGame.Framework.Portable" version="3.6.0.1625" targetFramework="portable45-net45+win8+wpa81" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="portable45-net45+win8+wpa81" />
</packages>