using System; using System.Collections.Generic; using Amphora; using Azimuth; using UnitTestSharp; namespace Konis.UnitTests { public class EmitterTests : TestFixture { public class MovingEmitter : Emitter { public readonly List Steps = new List(); public readonly List CountsAtEachStep = new List(); public readonly List BirthIndices = new List(); public float LifetimeGiven = 10f; public float VelocityXGiven; public MovingEmitter(EmitterDef def, RandomGenerator random, int columnCount = 0) : base(def, random, columnCount) { } protected override void Step(float seconds) { Steps.Add(seconds); CountsAtEachStep.Add(Store.Count); ParticleMotion.Integrate(Store, seconds); } protected override void Birth(int index) { BirthIndices.Add(index); Store.Items[index].Lifetime = LifetimeGiven; Store.Items[index].VelocityX = VelocityXGiven; } } private static MovingEmitter Started(int capacity, Variation rate, Variation lifespan = default(Variation)) { var def = new PlainEmitterDef { Capacity = capacity, Rate = rate, Lifespan = lifespan }; return new MovingEmitter(def, new ScriptedGenerator()); } public class CtorTests : TestFixture { public void NullDef_Throws() { CheckThrow(typeof(ArgumentNullException)); new MovingEmitter(null, new ScriptedGenerator()); } public void NullRandom_Throws() { CheckThrow(typeof(ArgumentNullException)); new MovingEmitter(new PlainEmitterDef(), null); } public void KeepsTheDef() { var def = new PlainEmitterDef(); CheckTrue(ReferenceEquals(def, new MovingEmitter(def, new ScriptedGenerator()).Def)); } public void MakesAStoreOfTheDefsCapacityAndTheColumnsAsked() { var emitter = new MovingEmitter( new PlainEmitterDef { Capacity = 7 }, new ScriptedGenerator(), columnCount: 2); CheckEqual(7, emitter.Store.Capacity); CheckEqual(2, emitter.Store.Columns.Length); } public void DrawsTheRateThenTheLifespan() { var def = new PlainEmitterDef { Rate = new Variation(0, 16), Lifespan = new Variation(0, 16) }; ScriptedGenerator random = new ScriptedGenerator( ScriptedGenerator.WordForUnit(0.25), ScriptedGenerator.WordForUnit(0.5)); var emitter = new MovingEmitter(def, random); CheckTrue(Math.Abs(12.0 - emitter.Rate) < 1e-9); CheckTrue(Math.Abs(8.0 - emitter.Lifespan) < 1e-9); CheckEqual(0, random.Remaining); } public void EmptyVariations_TakeNoRandomNumbers() { var def = new PlainEmitterDef { Rate = 5, Lifespan = 2 }; ScriptedGenerator random = ScriptedGenerator.OfUnits(0.5); var emitter = new MovingEmitter(def, random); CheckEqual(5.0, emitter.Rate); CheckEqual(2.0, emitter.Lifespan); CheckEqual(1, random.Remaining); } public void StartsAtAgeZeroWithAnIntensityOfOneAndNotFinished() { var emitter = Started(capacity: 1, rate: 1, lifespan: 1); CheckEqual(0.0, emitter.Age); CheckEqual(1f, emitter.Intensity); CheckFalse(emitter.IsFinished); } } public class PositionTests : TestFixture { public void IsWhereItWasPut() { MovingEmitter emitter = Started(capacity: 1, rate: 0); emitter.Position = new Vector3(1, 2, 3); CheckEqual(2.0, emitter.Position.Y.Value); } } public class IntensityTests : TestFixture { public void IsWhatItWasSet() { MovingEmitter emitter = Started(capacity: 1, rate: 0); emitter.Intensity = 2.5f; CheckEqual(2.5f, emitter.Intensity); } public void Zero_IsAllowed() { Started(capacity: 1, rate: 0).Intensity = 0f; } public void Negative_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Started(capacity: 1, rate: 0).Intensity = -1f; } public void NotANumber_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Started(capacity: 1, rate: 0).Intensity = float.NaN; } public void Infinite_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Started(capacity: 1, rate: 0).Intensity = float.PositiveInfinity; } } public class UpdateTests : TestFixture { public void ANegativeTime_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Started(capacity: 1, rate: 0).Update(-1f); } public void ATimeThatIsNotANumber_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Started(capacity: 1, rate: 0).Update(float.NaN); } public void AnInfiniteTime_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Started(capacity: 1, rate: 0).Update(float.PositiveInfinity); } public void AddsTheTimeToTheAgeAndStepsOnceByIt() { MovingEmitter emitter = Started(capacity: 1, rate: 0); emitter.Update(0.5f); emitter.Update(0.25f); CheckEqual(0.75, emitter.Age); CheckEqual(2, emitter.Steps.Count); CheckEqual(0.5f, emitter.Steps[0]); CheckEqual(0.25f, emitter.Steps[1]); } public void ATimeOfZero_StepsByZeroAndBirthsNothing() { MovingEmitter emitter = Started(capacity: 4, rate: 8); emitter.Update(0f); CheckEqual(0f, emitter.Steps[0]); CheckEqual(0, emitter.Store.Count); } public void StepsBeforeBirthing() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Update(0.5f); emitter.Update(0.5f); CheckEqual(0, emitter.CountsAtEachStep[0]); CheckEqual(4, emitter.CountsAtEachStep[1]); } public void BirthsWhatTheRateOwes() { MovingEmitter emitter = Started(capacity: 100, rate: 10); emitter.Update(1f); CheckEqual(10, emitter.Store.Count); CheckEqual(10, emitter.BirthIndices.Count); } public void GivesEachBirthTheAgeItWouldHaveHadIfBornPartWayThroughTheStep() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Update(0.5f); CheckEqual(4, emitter.Store.Count); CheckEqual(0.375f, emitter.Store.Items[0].Age); CheckEqual(0.25f, emitter.Store.Items[1].Age); CheckEqual(0.125f, emitter.Store.Items[2].Age); CheckEqual(0f, emitter.Store.Items[3].Age); } public void MovesEachBirthAlongItsVelocityForThatAge() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.VelocityXGiven = 4f; emitter.Update(0.5f); CheckEqual(1.5f, emitter.Store.Items[0].PositionX); CheckEqual(1f, emitter.Store.Items[1].PositionX); CheckEqual(0.5f, emitter.Store.Items[2].PositionX); CheckEqual(0f, emitter.Store.Items[3].PositionX); } public void ShowsTheBirthHookTheIndexOfTheParticleAdded() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Update(0.5f); CheckEqual(4, emitter.BirthIndices.Count); CheckEqual(0, emitter.BirthIndices[0]); CheckEqual(3, emitter.BirthIndices[3]); } public void CarriesTheFractionOfAParticleToTheNextStep() { MovingEmitter emitter = Started(capacity: 10, rate: 2); emitter.Update(0.75f); CheckEqual(1, emitter.Store.Count); CheckEqual(0.25f, emitter.Store.Items[0].Age); emitter.Update(0.75f); CheckEqual(3, emitter.Store.Count); CheckEqual(0.5f, emitter.Store.Items[1].Age); CheckEqual(0f, emitter.Store.Items[2].Age); } public void ARateOfZero_BirthsNothing() { MovingEmitter emitter = Started(capacity: 10, rate: 0); emitter.Update(5f); CheckEqual(0, emitter.Store.Count); } public void AnIntensityOfZero_BirthsNothing() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Intensity = 0f; emitter.Update(1f); CheckEqual(0, emitter.Store.Count); } public void ScalesTheRateByTheIntensity() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Intensity = 0.5f; emitter.Update(0.5f); CheckEqual(2, emitter.Store.Count); } public void DropsBirthsPastCapacityAndKeepsTheEarliest() { MovingEmitter emitter = Started(capacity: 3, rate: 8); emitter.Update(0.5f); CheckEqual(3, emitter.Store.Count); CheckEqual(0.375f, emitter.Store.Items[0].Age); CheckEqual(0.125f, emitter.Store.Items[2].Age); } public void DoesNotQueueBirthsDroppedForCapacity() { MovingEmitter emitter = Started(capacity: 3, rate: 8); emitter.Update(0.5f); emitter.Update(0f); CheckEqual(3, emitter.Store.Count); CheckEqual(3, emitter.BirthIndices.Count); } public void WithALimit_BirthsNoMoreThanItAndKeepsTheEarliest() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Update(0.5f, maxBirths: 2); CheckEqual(2, emitter.Store.Count); CheckEqual(0.375f, emitter.Store.Items[0].Age); CheckEqual(0.25f, emitter.Store.Items[1].Age); } public void ALimitOfZero_BirthsNothing() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Update(0.5f, maxBirths: 0); CheckEqual(0, emitter.Store.Count); } public void DoesNotQueueBirthsDroppedForTheLimit() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.Update(0.5f, maxBirths: 1); emitter.Update(0f); CheckEqual(1, emitter.Store.Count); } public void ANegativeLimit_Throws() { MovingEmitter emitter = Started(capacity: 10, rate: 8); CheckThrow(typeof(ArgumentOutOfRangeException)); emitter.Update(0.5f, maxBirths: -1); } public void AHugeRate_FillsTheStoreWithoutOverflowing() { MovingEmitter emitter = Started(capacity: 3, rate: 1e18); emitter.Update(1f); CheckEqual(3, emitter.Store.Count); } public void StopsBirthingAtTheLifespan() { MovingEmitter emitter = Started(capacity: 20, rate: 8, lifespan: 1); emitter.Update(2f); CheckEqual(8, emitter.Store.Count); CheckEqual(1.875f, emitter.Store.Items[0].Age); } public void BirthsNothingOnceTheLifespanHasPassed() { MovingEmitter emitter = Started(capacity: 20, rate: 8, lifespan: 1); emitter.Update(2f); emitter.Update(1f); CheckEqual(8, emitter.Store.Count); } public void BirthsOnlyTheRestOfTheLifespanInTheStepThatCrossesIt() { MovingEmitter emitter = Started(capacity: 20, rate: 8, lifespan: 1); emitter.Update(0.75f); CheckEqual(6, emitter.Store.Count); emitter.Update(0.75f); CheckEqual(8, emitter.Store.Count); } public void ALifespanOfZero_NeverStopsBirthing() { MovingEmitter emitter = Started(capacity: 2000, rate: 8); emitter.LifetimeGiven = 1000f; emitter.Update(100f); CheckEqual(800, emitter.Store.Count); } public void RemovesBirthsThatAreAlreadyPastTheirLifetime() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.LifetimeGiven = 0.2f; emitter.Update(0.5f); CheckEqual(4, emitter.BirthIndices.Count); CheckEqual(2, emitter.Store.Count); } public void RemovesParticlesThatLiveOutTheirLifetimeDuringTheStep() { MovingEmitter emitter = Started(capacity: 10, rate: 8); emitter.LifetimeGiven = 1f; emitter.Update(0.5f); emitter.Update(0.75f); CheckEqual(4 + 6 - 2, emitter.Store.Count); } } public class PreWarmTests : TestFixture { public void StepsByTheStepSizeThenByTheRemainder() { MovingEmitter emitter = Started(capacity: 1, rate: 0); emitter.PreWarm(2.5f, stepSize: 1f); CheckEqual(3, emitter.Steps.Count); CheckEqual(1f, emitter.Steps[0]); CheckEqual(1f, emitter.Steps[1]); CheckEqual(0.5f, emitter.Steps[2]); CheckEqual(2.5, emitter.Age); } public void ATimeThatIsAWholeNumberOfSteps_TakesThatManySteps() { MovingEmitter emitter = Started(capacity: 1, rate: 0); emitter.PreWarm(2f, stepSize: 1f); CheckEqual(2, emitter.Steps.Count); CheckEqual(1f, emitter.Steps[1]); } public void ATimeOfZero_StepsOnceByZero() { MovingEmitter emitter = Started(capacity: 1, rate: 0); emitter.PreWarm(0f, stepSize: 1f); CheckEqual(1, emitter.Steps.Count); CheckEqual(0f, emitter.Steps[0]); } public void LeavesTheEmitterWithTheParticlesItWouldHaveHad() { MovingEmitter emitter = Started(capacity: 100, rate: 8); emitter.PreWarm(2f, stepSize: 0.5f); CheckEqual(16, emitter.Store.Count); } public void ANegativeTime_Throws() { MovingEmitter emitter = Started(capacity: 1, rate: 0); CheckThrow(typeof(ArgumentOutOfRangeException)); emitter.PreWarm(-1f, stepSize: 1f); } public void AnInfiniteTime_Throws() { MovingEmitter emitter = Started(capacity: 1, rate: 0); CheckThrow(typeof(ArgumentOutOfRangeException)); emitter.PreWarm(float.PositiveInfinity, stepSize: 1f); } public void AStepOfZero_Throws() { MovingEmitter emitter = Started(capacity: 1, rate: 0); CheckThrow(typeof(ArgumentOutOfRangeException)); emitter.PreWarm(1f, stepSize: 0f); } public void AnInfiniteStep_Throws() { MovingEmitter emitter = Started(capacity: 1, rate: 0); CheckThrow(typeof(ArgumentOutOfRangeException)); emitter.PreWarm(1f, stepSize: float.PositiveInfinity); } } public class UpdateWithFxTimeTests : TestFixture { private static FxTime Frame() { return new FxTime(Duration.FromSeconds(1), Duration.FromSeconds(0.5), speed: 2); } public void OnAGameClock_StepsByTheGameTime() { var def = new PlainEmitterDef { Capacity = 1, Clock = ClockBase.Game }; var emitter = new MovingEmitter(def, new ScriptedGenerator()); emitter.Update(Frame()); CheckEqual(0.5f, emitter.Steps[0]); } public void OnARealClock_StepsByTheRealTime() { var def = new PlainEmitterDef { Capacity = 1, Clock = ClockBase.Real }; var emitter = new MovingEmitter(def, new ScriptedGenerator()); emitter.Update(Frame()); CheckEqual(1f, emitter.Steps[0]); } public void OnAGameNormalClock_StepsByTheGameTimeAtNormalSpeed() { var def = new PlainEmitterDef { Capacity = 1, Clock = ClockBase.GameNormal }; var emitter = new MovingEmitter(def, new ScriptedGenerator()); emitter.Update(Frame()); CheckEqual(0.25f, emitter.Steps[0]); } } public class IsFinishedTests : TestFixture { public void WhileTheLifespanHasNotEnded_IsFalse() { MovingEmitter emitter = Started(capacity: 4, rate: 0, lifespan: 2); emitter.Update(1f); CheckFalse(emitter.IsFinished); } public void WhileParticlesRemainAfterTheLifespan_IsFalse() { MovingEmitter emitter = Started(capacity: 4, rate: 1, lifespan: 1); emitter.LifetimeGiven = 0.5f; emitter.Update(1f); CheckEqual(1, emitter.Store.Count); CheckFalse(emitter.IsFinished); } public void OnceTheLifespanHasEndedAndTheParticlesAreGone_IsTrue() { MovingEmitter emitter = Started(capacity: 4, rate: 1, lifespan: 1); emitter.LifetimeGiven = 0.5f; emitter.Update(1f); emitter.Update(0.5f); CheckEqual(0, emitter.Store.Count); CheckTrue(emitter.IsFinished); } public void WithALifespanOfZero_IsNeverTrue() { MovingEmitter emitter = Started(capacity: 4, rate: 0); emitter.Update(1000f); CheckFalse(emitter.IsFinished); } } } }