using System; using Amphora; using Azimuth; using Prism; using UnitTestSharp; namespace Konis.UnitTests { public class StandardEmitterTests : TestFixture { private static StandardEmitterDef Def( Variation speed = default(Variation), Variation heading = default(Variation), Variation elevation = default(Variation), Variation gravity = default(Variation), Variation drag = default(Variation), Axis up = Axis.Z) { return new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, Speed = speed, Heading = heading, Elevation = elevation, Gravity = gravity, Drag = drag, Up = up, TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; } private static StandardEmitter OneParticleBorn(StandardEmitterDef def, ScriptedGenerator random = null) { var emitter = new StandardEmitter(def, random ?? new ScriptedGenerator()); emitter.Update(1f); return emitter; } private static bool Near(double expected, double actual) { return Math.Abs(expected - actual) < 1e-5; } public class CtorTests : TestFixture { public void NullDef_Throws() { CheckThrow(typeof(ArgumentNullException)); new StandardEmitter(null, new ScriptedGenerator()); } public void NullRandom_Throws() { CheckThrow(typeof(ArgumentNullException)); new StandardEmitter(Def(), null); } public void ADefThatFailsItsChecks_Throws() { CheckThrow(typeof(InvalidOperationException)); new StandardEmitter(new StandardEmitterDef(), new ScriptedGenerator()); } public void KeepsTheDef() { StandardEmitterDef def = Def(); CheckTrue(ReferenceEquals(def, new StandardEmitter(def, new ScriptedGenerator()).Def)); } public void MakesAChannelOfEachKindFromTheDef() { var emitter = new StandardEmitter(Def(gravity: 10, drag: new Variation(0, 1)), new ScriptedGenerator()); CheckEqual(ChannelMode.Constant, emitter.Size.Mode); CheckEqual(ChannelMode.Zero, emitter.Spin.Mode); CheckEqual(ChannelMode.Constant, emitter.Gravity.Mode); CheckEqual(ChannelMode.PerParticle, emitter.Drag.Mode); } public void MakesAColumnOnlyForEachPerParticleChannel() { StandardEmitterDef def = Def(gravity: new Variation(0, 1), drag: new Variation(0, 1)); CheckEqual(2, new StandardEmitter(def, new ScriptedGenerator()).Store.Columns.Length); CheckEqual(0, new StandardEmitter(Def(), new ScriptedGenerator()).Store.Columns.Length); } public void TakesColumnsInTheOrderSizeSpinGravityDrag() { var def = new StandardEmitterDef { Capacity = 1, Size = new Variation(1, 2), AngularVelocity = new Variation(1, 2), Gravity = new Variation(1, 2), Drag = new Variation(1, 2), TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; var emitter = new StandardEmitter(def, new ScriptedGenerator()); CheckEqual(0, emitter.Size.Column); CheckEqual(1, emitter.Spin.Column); CheckEqual(2, emitter.Gravity.Column); CheckEqual(3, emitter.Drag.Column); } } public class CreateEmitterTests : TestFixture { public void MakesAStandardEmitterOfTheDef() { StandardEmitterDef def = Def(); Emitter emitter = def.CreateEmitter(new ScriptedGenerator()); CheckTrue(emitter is StandardEmitter); CheckTrue(ReferenceEquals(def, emitter.Def)); } public void WithANullGenerator_Throws() { CheckThrow(typeof(ArgumentNullException)); Def().CreateEmitter(null); } } public class BirthTests : TestFixture { public void GivesTheParticleItsLifetime() { StandardEmitter emitter = OneParticleBorn(Def()); CheckEqual(2f, emitter.Store.Items[0].Lifetime); } public void ASpeedWithNoHeadingOrElevation_LeavesAlongTheFirstAxisAroundUp() { StandardEmitter emitter = OneParticleBorn(Def(speed: 10)); CheckEqual(10f, emitter.Store.Items[0].VelocityX); CheckEqual(0f, emitter.Store.Items[0].VelocityY); CheckEqual(0f, emitter.Store.Items[0].VelocityZ); } public void AQuarterTurnOfHeading_LeavesAlongTheSecondAxis() { StandardEmitter emitter = OneParticleBorn(Def(speed: 10, heading: 0.25)); CheckTrue(Near(0, emitter.Store.Items[0].VelocityX)); CheckTrue(Near(10, emitter.Store.Items[0].VelocityY)); CheckTrue(Near(0, emitter.Store.Items[0].VelocityZ)); } public void AHalfTurnOfHeading_LeavesAgainstTheFirstAxis() { StandardEmitter emitter = OneParticleBorn(Def(speed: 10, heading: 0.5)); CheckTrue(Near(-10, emitter.Store.Items[0].VelocityX)); } public void AQuarterTurnOfElevation_LeavesAlongUp() { StandardEmitter emitter = OneParticleBorn(Def(speed: 10, elevation: 0.25)); CheckTrue(Near(0, emitter.Store.Items[0].VelocityX)); CheckTrue(Near(0, emitter.Store.Items[0].VelocityY)); CheckTrue(Near(10, emitter.Store.Items[0].VelocityZ)); } public void AnEighthOfATurnOfElevation_SplitsTheSpeedBetweenUpAndTheFirstAxis() { StandardEmitter emitter = OneParticleBorn(Def(speed: 10, elevation: 0.125)); double share = 10 * Math.Sqrt(0.5); CheckTrue(Near(share, emitter.Store.Items[0].VelocityX)); CheckTrue(Near(share, emitter.Store.Items[0].VelocityZ)); } public void WithXUp_UpIsXAndHeadingTurnsFromYTowardsZ() { StandardEmitterDef atRest = Def(speed: 10, up: Axis.X); StandardEmitterDef turned = Def(speed: 10, heading: 0.25, up: Axis.X); StandardEmitterDef raised = Def(speed: 10, elevation: 0.25, up: Axis.X); Particle rest = OneParticleBorn(atRest).Store.Items[0]; Particle turn = OneParticleBorn(turned).Store.Items[0]; Particle rise = OneParticleBorn(raised).Store.Items[0]; CheckTrue(Near(10, rest.VelocityY)); CheckTrue(Near(10, turn.VelocityZ)); CheckTrue(Near(10, rise.VelocityX)); } public void WithYUp_UpIsYAndHeadingTurnsFromZTowardsX() { StandardEmitterDef atRest = Def(speed: 10, up: Axis.Y); StandardEmitterDef turned = Def(speed: 10, heading: 0.25, up: Axis.Y); StandardEmitterDef raised = Def(speed: 10, elevation: 0.25, up: Axis.Y); Particle rest = OneParticleBorn(atRest).Store.Items[0]; Particle turn = OneParticleBorn(turned).Store.Items[0]; Particle rise = OneParticleBorn(raised).Store.Items[0]; CheckTrue(Near(10, rest.VelocityZ)); CheckTrue(Near(10, turn.VelocityX)); CheckTrue(Near(10, rise.VelocityY)); } public void DrawsTheLifetimeBeforeTheSpeed() { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = new Variation(1, 17), Speed = new Variation(0, 16), TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; ScriptedGenerator random = new ScriptedGenerator( ScriptedGenerator.WordForUnit(0.5), ScriptedGenerator.WordForUnit(0.25)); StandardEmitter emitter = OneParticleBorn(def, random); CheckTrue(Near(9, emitter.Store.Items[0].Lifetime)); CheckTrue(Near(12, emitter.Store.Items[0].VelocityX)); CheckEqual(0, random.Remaining); } public void DrawsTheHeadingBeforeTheElevation() { StandardEmitterDef def = Def( speed: 10, heading: new Variation(0, 0.5), elevation: new Variation(0, 0.5)); ScriptedGenerator random = new ScriptedGenerator( ScriptedGenerator.WordForUnit(0.5), ScriptedGenerator.WordForUnit(0.0)); StandardEmitter emitter = OneParticleBorn(def, random); CheckTrue(Near(0, emitter.Store.Items[0].VelocityX)); CheckTrue(Near(-10, emitter.Store.Items[0].VelocityY)); CheckTrue(Near(0, emitter.Store.Items[0].VelocityZ)); CheckEqual(0, random.Remaining); } public void DrawsTheSizeThenTheSpinThenTheGravityThenTheDragIntoTheirColumns() { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, Size = new Variation(0.5, 16.5), AngularVelocity = new Variation(0, 16), Gravity = new Variation(0, 16), Drag = new Variation(0, 16), TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; ScriptedGenerator random = new ScriptedGenerator( ScriptedGenerator.WordForUnit(0.5), ScriptedGenerator.WordForUnit(0.25), ScriptedGenerator.WordForUnit(0.75), ScriptedGenerator.WordForUnit(0.125)); StandardEmitter emitter = OneParticleBorn(def, random); CheckTrue(Near(8.5, emitter.Store.Columns[0][0])); CheckTrue(Near(12, emitter.Store.Columns[1][0])); CheckTrue(Near(4, emitter.Store.Columns[2][0])); CheckTrue(Near(14, emitter.Store.Columns[3][0])); CheckEqual(0, random.Remaining); } public void TakesNoRandomNumberWhenEveryVariationIsEmpty() { ScriptedGenerator random = ScriptedGenerator.OfUnits(0.5); OneParticleBorn(Def(speed: 3, heading: 0.1, elevation: 0.1, gravity: 1, drag: 1), random); CheckEqual(1, random.Remaining); } } public class StepTests : TestFixture { private static StandardEmitter Run(StandardEmitterDef def, float seconds) { StandardEmitter emitter = OneParticleBorn(def); emitter.Update(seconds); return emitter; } public void WithNoGravityOrDrag_MovesTheParticleAlongItsVelocity() { StandardEmitter emitter = Run(Def(speed: 8), 0.5f); CheckEqual(4f, emitter.Store.Items[0].PositionX); CheckEqual(8f, emitter.Store.Items[0].VelocityX); CheckEqual(0.5f, emitter.Store.Items[0].Age); } public void ConstantGravity_PullsTowardsTheNegativeEndOfUp() { StandardEmitter emitter = Run(Def(gravity: 10), 0.5f); CheckEqual(-5f, emitter.Store.Items[0].VelocityZ); CheckEqual(-2.5f, emitter.Store.Items[0].PositionZ); } public void ConstantGravity_PullsAlongAnUpOfX() { StandardEmitter emitter = Run(Def(gravity: 10, up: Axis.X), 0.5f); CheckEqual(-5f, emitter.Store.Items[0].VelocityX); CheckEqual(0f, emitter.Store.Items[0].VelocityZ); } public void ConstantDrag_SlowsTheParticle() { StandardEmitter emitter = Run(Def(speed: 8, drag: 0.5), 0.5f); CheckEqual(6f, emitter.Store.Items[0].VelocityX); CheckEqual(3f, emitter.Store.Items[0].PositionX); } public void AppliesGravityThenDragThenMotion() { StandardEmitter emitter = Run(Def(gravity: 10, drag: 0.5), 0.5f); CheckEqual(-3.75f, emitter.Store.Items[0].VelocityZ); CheckEqual(-1.875f, emitter.Store.Items[0].PositionZ); } public void PerParticleGravity_PullsByTheParticlesOwnColumnEntry() { StandardEmitterDef def = Def(gravity: new Variation(0, 16)); ScriptedGenerator random = ScriptedGenerator.OfUnits(0.5); StandardEmitter emitter = OneParticleBorn(def, random); emitter.Update(0.5f); CheckTrue(Near(-4, emitter.Store.Items[0].VelocityZ)); CheckTrue(Near(-2, emitter.Store.Items[0].PositionZ)); } public void PerParticleDrag_SlowsByTheParticlesOwnColumnEntry() { StandardEmitterDef def = Def(speed: 8, drag: new Variation(0, 1)); ScriptedGenerator random = ScriptedGenerator.OfUnits(0.5); StandardEmitter emitter = OneParticleBorn(def, random); emitter.Update(0.5f); CheckTrue(Near(8 * (1 - 0.5 * 0.5), emitter.Store.Items[0].VelocityX)); } } public class RotationOfTests : TestFixture { public void WithNoSpin_IsZero() { StandardEmitter emitter = OneParticleBorn(Def()); emitter.Update(0.5f); CheckEqual(0f, emitter.RotationOf(0)); } public void WithAConstantSpin_IsTheSpinTimesTheAge() { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, AngularVelocity = 2, TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; StandardEmitter emitter = OneParticleBorn(def); emitter.Update(0.5f); CheckEqual(1f, emitter.RotationOf(0)); } public void WithAPerParticleSpin_IsThatParticlesSpinTimesItsAge() { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, AngularVelocity = new Variation(0, 16), TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; StandardEmitter emitter = OneParticleBorn(def, ScriptedGenerator.OfUnits(0.5)); emitter.Update(0.5f); CheckTrue(Near(4, emitter.RotationOf(0))); } } public class SizeOfTests : TestFixture { private static StandardEmitter WithSize( Variation size, Func overLife, ScriptedGenerator random) { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, Size = size, SizeOverLife = overLife, TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; StandardEmitter emitter = OneParticleBorn(def, random); emitter.Update(0.5f); return emitter; } public void WithNoSizeOverLife_IsTheStartingSize() { StandardEmitter emitter = WithSize(2, null, new ScriptedGenerator()); CheckEqual(2f, emitter.SizeOf(0)); } public void IsTheStartingSizeTimesTheSizeOverLifeAtTheFractionOfTheLifetimeGone() { StandardEmitter emitter = WithSize(2, fraction => 1 + fraction, new ScriptedGenerator()); CheckEqual(2.5f, emitter.SizeOf(0)); } public void WithAPerParticleSize_UsesThatParticlesSize() { StandardEmitter emitter = WithSize(new Variation(0.5, 16.5), null, ScriptedGenerator.OfUnits(0.5)); CheckTrue(Near(8.5, emitter.SizeOf(0))); } public void ForAParticlePastItsLifetime_UsesTheEndOfTheSizeOverLife() { StandardEmitter emitter = WithSize(2, fraction => 1 + fraction, new ScriptedGenerator()); emitter.Store.Items[0].Age = 5f; CheckEqual(4f, emitter.SizeOf(0)); } } public class TintOfTests : TestFixture { private static StandardEmitter WithTint(Gradient gradient, Func alphaOverLife) { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, AlphaOverLife = alphaOverLife, TintOverLife = new GradientDef { Gradient = gradient }, }; StandardEmitter emitter = OneParticleBorn(def); emitter.Update(0.5f); return emitter; } private static Gradient RedConstant() { return Gradient.Constant(Colors.Red); } public void ForAParticlePastItsLifetime_IsTheLastColor() { StandardEmitter emitter = WithTint(Gradient.Between(Colors.Black, Colors.White), null); emitter.Store.Items[0].Age = 5f; CheckEqual(Colors.White.ToArgb(), emitter.TintOf(0)); } public void ForAParticleJustBorn_IsTheFirstColorOfTheGradient() { StandardEmitter emitter = WithTint(Gradient.Between(Colors.Black, Colors.White), null); emitter.Store.Items[0].Age = 0f; CheckEqual(Colors.Black.ToArgb(), emitter.TintOf(0)); } public void WithNoAlphaOverLife_LeavesTheGradientColorAlone() { CheckEqual(Colors.Red.ToArgb(), WithTint(RedConstant(), null).TintOf(0)); } public void ScalesTheAlphaByTheAlphaOverLifeAndKeepsTheColor() { StandardEmitter emitter = WithTint(RedConstant(), fraction => 1 - fraction); CheckEqual((191 << 24) | (Colors.Red.ToArgb() & 0xFFFFFF), emitter.TintOf(0)); } public void AnAlphaOverLifeAboveOne_IsHeldAtFullAlpha() { CheckEqual(Colors.Red.ToArgb(), WithTint(RedConstant(), fraction => 2).TintOf(0)); } public void AnAlphaOverLifeBelowZero_IsHeldAtNoAlpha() { CheckEqual(Colors.Red.ToArgb() & 0xFFFFFF, WithTint(RedConstant(), fraction => -1).TintOf(0)); } } public class AlphaOfTests : TestFixture { private static StandardEmitter WithAlpha(Func overLife) { var def = new StandardEmitterDef { Capacity = 10, Rate = 1, Lifetime = 2, AlphaOverLife = overLife, TintOverLife = new GradientDef { Gradient = Gradient.Constant(Colors.White) }, }; StandardEmitter emitter = OneParticleBorn(def); emitter.Update(0.5f); return emitter; } public void WithNoAlphaOverLife_IsOne() { CheckEqual(1f, WithAlpha(null).AlphaOf(0)); } public void IsTheAlphaOverLifeAtTheFractionOfTheLifetimeGone() { CheckEqual(0.75f, WithAlpha(fraction => 1 - fraction).AlphaOf(0)); } } } }