public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Color4) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var colField = pool.GetField(ParticleFields.Color4); var rndField = pool.GetField(ParticleFields.RandomSeed); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var color = Color4.Lerp(ColorMin, ColorMax, randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset)); // Premultiply alpha // This can't be done in advance for ColorMin and ColorMax because it will change the math color.R *= color.A; color.G *= color.A; color.B *= color.A; (*((Color4*)particle[colField])) = color; i = (i + 1) % maxCapacity; } }
public void AddEmitter(ParticleEmitter emitter) { emitters.Add(emitter); ParticlePool pool = new ParticlePool(this, maxParticlesPerEmitter); CollectionUtils.Put(particlesByEmitter, emitter, pool); }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Order)) return; var orderField = pool.GetField(ParticleFields.Order); var childOrderField = pool.GetField(ParticleFields.ChildOrder); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); (*((uint*)particle[orderField])) = spawnOrder++; // Will loop every so often, but the loop condition should be unreachable for normal games if (childOrderField.IsValid()) (*((uint*)particle[childOrderField])) = 0; i = (i + 1) % maxCapacity; } // Increase the group by one spawnOrder = (spawnOrder >> SpawnOrderConst.GroupBitOffset); spawnOrder++; spawnOrder = (spawnOrder << SpawnOrderConst.GroupBitOffset); }
public ParticleFactory(int limit, int particlePerCreate, IParticleSetting setting, params TextureRegion[] regions) { this.pool = new ParticlePool(regions); particles = new List<Particle>(); this.limit = limit; this.particlePerCreate = particlePerCreate; if (setting == null) { throw new Exception("setting must be not null"); } particleSetting = setting; }
/// <inheritdoc /> public override void Update(float dt, ParticlePool pool) { if (!pool.FieldExists(ParticleFields.Angle) || !pool.FieldExists(ParticleFields.Life)) return; if (SamplerOptional == null) { UpdateSingleSampler(pool); return; } UpdateDoubleSampler(pool); }
/// <summary> /// Updates the field by sampling a single value over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateSingleSampler(ParticlePool pool) { var sizeField = pool.GetField(ParticleFields.Size); var lifeField = pool.GetField(ParticleFields.Life); SamplerMain.UpdateChanges(); foreach (var particle in pool) { var life = 1f - (*((float*)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) (*((float*)particle[sizeField])) = WorldScale.X * SamplerMain.Evaluate(life); } }
static void Main(string[] args) { ParticlePool particlePool = new ParticlePool(); particlePool.Create(5,1,3,2,1); particlePool.Create(5,1,3,2,6); particlePool.Create(5,1,3,2,2); particlePool.Animate(); particlePool.Animate(); particlePool.Animate(); particlePool.Animate(); Console.ReadLine(); // (I didn't test it with the ParticlePoolWithFreeList ...) }
public override unsafe void Update(float dt, ParticlePool pool) { if (dt <= MathUtil.ZeroTolerance) return; if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.OldPosition) || !pool.FieldExists(ParticleFields.Direction)) return; var posField = pool.GetField(ParticleFields.Position); var oldField = pool.GetField(ParticleFields.OldPosition); var dirField = pool.GetField(ParticleFields.Direction); foreach (var particle in pool) { (*((Vector3*)particle[dirField])) = ((*((Vector3*)particle[posField])) - (*((Vector3*)particle[oldField]))) / dt; } }
public override unsafe void Update(float dt, ParticlePool pool) { if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.Velocity)) return; var posField = pool.GetField(ParticleFields.Position); var velField = pool.GetField(ParticleFields.Velocity); var deltaVel = GravitationalAcceleration * dt; var deltaPos = deltaVel * (dt * 0.5f); foreach (var particle in pool) { (*((Vector3*)particle[posField])) += deltaPos; (*((Vector3*)particle[velField])) += deltaVel; } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Angle) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var rotField = pool.GetField(ParticleFields.Angle); var rndField = pool.GetField(ParticleFields.RandomSeed); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); (*((float*)particle[rotField])) = angularRotationStart + angularRotationStep * randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); i = (i + 1) % maxCapacity; } }
public override unsafe void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.Velocity) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var posField = pool.GetField(ParticleFields.Position); var velField = pool.GetField(ParticleFields.Velocity); var rndField = pool.GetField(ParticleFields.RandomSeed); var range = (float)(Angle * Math.PI / 180f); var magnitude = WorldScale.X; var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var x = (randSeed.GetFloat(RandomOffset.Offset2A + SeedOffset) - 0.5f) * range; var z = (randSeed.GetFloat(RandomOffset.Offset2B + SeedOffset) - 0.5f) * range; var u = (randSeed.GetFloat(RandomOffset.Offset2A + SeedOffset) - 0.5f) * range; var v = (randSeed.GetFloat(RandomOffset.Offset2B + SeedOffset) - 0.5f) * Math.PI; // var particleRandPos = new Vector3(x, 1, z); var xz = (float)Math.Sin(u); var particleRandPos = new Vector3((float)Math.Cos(v) * xz, (float)Math.Sqrt(1 - u * u), (float)Math.Sin(v) * xz); particleRandPos.Normalize(); particleRandPos *= magnitude; WorldRotation.Rotate(ref particleRandPos); (*((Vector3*)particle[posField])) = particleRandPos + WorldPosition; (*((Vector3*)particle[velField])) = particleRandPos * Strength; i = (i + 1) % maxCapacity; } }
/// <summary> /// Updates the field by sampling a single value over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateSingleSampler(ParticlePool pool) { var colorField = pool.GetField(ParticleFields.Color); var lifeField = pool.GetField(ParticleFields.Life); SamplerMain.UpdateChanges(); foreach (var particle in pool) { var life = 1f - (*((float*)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) var color = SamplerMain.Evaluate(life); // Premultiply alpha color.R *= color.A; color.G *= color.A; color.B *= color.A; (*((Color4*)particle[colorField])) = color; } }
/// <summary> /// Updates the field by interpolating between two sampled values over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateDoubleSampler(ParticlePool pool) { var sizeField = pool.GetField(ParticleFields.Size); var lifeField = pool.GetField(ParticleFields.Life); var randField = pool.GetField(ParticleFields.RandomSeed); SamplerMain.UpdateChanges(); SamplerOptional.UpdateChanges(); foreach (var particle in pool) { var life = 1f - (*((float *)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) var randSeed = particle.Get(randField); var lerp = randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); var size1 = SamplerMain.Evaluate(life); var size2 = SamplerOptional.Evaluate(life); (*((float *)particle[sizeField])) = WorldScale.X * (size1 + (size2 - size1) * lerp); } }
public void ReleaseAll(ParticleEmitter emitter) { if (particlesByEmitter.Count != 0) { IEnumerator <ParticlePool> it = particlesByEmitter.Values.GetEnumerator(); while (it.MoveNext()) { ParticlePool pool = it.Current; for (int i = 0; i < pool.particles.Length; i++) { if (pool.particles[i].InUse()) { if (pool.particles[i].GetEmitter() == emitter) { pool.particles[i].SetLife(-1); Release(pool.particles[i]); } } } } } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Angle) || !pool.FieldExists(ParticleFields.RandomSeed)) { return; } var rotField = pool.GetField(ParticleFields.Angle); var rndField = pool.GetField(ParticleFields.RandomSeed); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); (*((float *)particle[rotField])) = angularRotationStart + angularRotationStep * randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); i = (i + 1) % maxCapacity; } }
public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Size) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var sizeField = pool.GetField(ParticleFields.Size); var rndField = pool.GetField(ParticleFields.RandomSeed); var minSize = WorldScale.X * RandomSize.X; var sizeGap = WorldScale.X * RandomSize.Y - minSize; var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); (*((float*)particle[sizeField])) = minSize + sizeGap * randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); i = (i + 1) % maxCapacity; } }
public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Color4) || !pool.FieldExists(ParticleFields.RandomSeed)) { return; } var colField = pool.GetField(ParticleFields.Color4); var rndField = pool.GetField(ParticleFields.RandomSeed); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); (*((Color4 *)particle[colField])) = Color4.Lerp(ColorMin, ColorMax, randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset)); i = (i + 1) % maxCapacity; } }
/** * Init pool */ private void initPool(ParticlePool particle) { //check there was no same name for particle if (m_ParticleDictionary.ContainsKey(particle.name)) { Debug.LogWarning("A particle with name '" + particle.name + "' has already been added"); } else { //add to dictionary m_ParticleDictionary.Add(particle.name, particle); //initialize the GameObjects for pooling later List <ParticleClip> list = new List <ParticleClip>(); for (int i = 0; i < particle.poolNum; i++) { SpawnForPool(particle.particlePrefab, list); } //add to particle pool m_ParticlePool.Add(particle.name, list); } }
/// <summary> /// Updates the field by sampling a single value over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateSingleSampler(ParticlePool pool) { var sizeField = pool.GetField(ParticleFields.Size); var lifeField = pool.GetField(ParticleFields.Life); int count = pool.NextFreeIndex; for (int i = 0; i < count; i++) { Particle particle = pool.FromIndex(i); var life = 1f - (*((float *)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) if (pool.SpecificSizes != null) { (*((float *)particle[sizeField])) = WorldScale.X * SamplerMain.Evaluate(life) * pool.SpecificSizes[i]; } else { (*((float *)particle[sizeField])) = WorldScale.X * SamplerMain.Evaluate(life); } } }
public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Direction) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var dirField = pool.GetField(ParticleFields.Direction); var rndField = pool.GetField(ParticleFields.RandomSeed); var leftCorner = DirectionMin * WorldScale; var xAxis = new Vector3(DirectionMax.X * WorldScale.X - leftCorner.X, 0, 0); var yAxis = new Vector3(0, DirectionMax.Y * WorldScale.Y - leftCorner.Y, 0); var zAxis = new Vector3(0, 0, DirectionMax.Z * WorldScale.Z - leftCorner.Z); if (!WorldRotation.IsIdentity) { WorldRotation.Rotate(ref leftCorner); WorldRotation.Rotate(ref xAxis); WorldRotation.Rotate(ref yAxis); WorldRotation.Rotate(ref zAxis); } var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandDir = leftCorner; particleRandDir += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandDir += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandDir += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); (*((Vector3*)particle[dirField])) = particleRandDir; i = (i + 1) % maxCapacity; } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Quaternion) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var rotField = pool.GetField(ParticleFields.Quaternion); var rndField = pool.GetField(ParticleFields.RandomSeed); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var randomRotation = Quaternion.Slerp(RotationQuaternionMin, RotationQuaternionMax, randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset)); // Results in errors ("small" quaternion) when interpolation -90 to +90 degree rotations, so we have to normalize it randomRotation.Normalize(); (*((Quaternion*)particle[rotField])) = randomRotation * WorldRotation; i = (i + 1) % maxCapacity; } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Order)) { return; } // Collect the total number of living particles in the parent pool which have a Order field var parentPool = Parent?.Pool; var parentParticlesCount = parentPool?.LivingParticles ?? 0; var orderFieldParent = parentPool?.GetField(ParticleFields.Order) ?? ParticleFieldAccessor <uint> .Invalid(); var childOrderFieldParent = parentPool?.GetField(ParticleFields.ChildOrder) ?? ParticleFieldAccessor <uint> .Invalid(); if (!orderFieldParent.IsValid()) { parentParticlesCount = 0; } var spawnControlField = GetSpawnControlField(); var orderField = pool.GetField(ParticleFields.Order); var randomField = pool.GetField(ParticleFields.RandomSeed); var sequentialParentIndex = 0; var sequentialParentParticles = 0; var parentIndex = 0; var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); // Will loop every so often, but the loop condition should be unreachable for normal games uint particleOrder = spawnOrder++; if (parentParticlesCount > 0) { uint parentParticleOrder = 0; // Spawn is fixed - parent particles have spawned a very specific number of children each if (spawnControlField.IsValid()) { while (sequentialParentParticles == 0) { // Early out - no more fixed number children. Rest of the particles (if any) are skipped intentionally if (sequentialParentIndex >= parentParticlesCount) { return; } parentIndex = sequentialParentIndex; var tempParentParticle = parentPool.FromIndex(parentIndex); sequentialParentIndex++; var childrenAttribute = (*((ParticleChildrenAttribute *)tempParentParticle[spawnControlField])); sequentialParentParticles = (int)childrenAttribute.ParticlesToEmit; } sequentialParentParticles--; var parentParticle = parentPool.FromIndex(parentIndex); parentParticleOrder = (*((uint *)parentParticle[orderFieldParent])); if (childOrderFieldParent.IsValid()) { particleOrder = (*((uint *)parentParticle[childOrderFieldParent])); (*((uint *)parentParticle[childOrderFieldParent])) = (particleOrder + 1); particleOrder = (particleOrder & 0x0000FFFF) | ((parentParticleOrder << 16) & 0xFFFF0000); } else { particleOrder = (particleOrder & 0x000FFFFF) | ((parentParticleOrder << 20) & 0xFFF00000); } } // Spawn is not fixed - pick a parent at random else { var randSeed = particle.Get(randomField); parentIndex = (int)(parentParticlesCount * randSeed.GetFloat(RandomOffset.Offset1A + ParentSeedOffset)); var parentParticle = parentPool.FromIndex(parentIndex); parentParticleOrder = (*((uint *)parentParticle[orderFieldParent])); if (childOrderFieldParent.IsValid()) { particleOrder = (*((uint *)parentParticle[childOrderFieldParent])); (*((uint *)parentParticle[childOrderFieldParent])) = (particleOrder + 1); particleOrder = (particleOrder & 0x0000FFFF) | ((parentParticleOrder << 16) & 0xFFFF0000); } else { particleOrder = (particleOrder & 0x000FFFFF) | ((parentParticleOrder << 20) & 0xFFF00000); } } } (*((uint *)particle[orderField])) = particleOrder; i = (i + 1) % maxCapacity; } }
void Start() { // 0 = Normal crate // 1 = TNT crate particlePool = new ParticlePool(effects[0], effects[1], 5); }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.RandomSeed)) { return; } var posField = pool.GetField(ParticleFields.Position); var oldField = pool.GetField(ParticleFields.OldPosition); var rndField = pool.GetField(ParticleFields.RandomSeed); var leftCorner = PositionMin * WorldScale; var xAxis = new Vector3(PositionMax.X * WorldScale.X - leftCorner.X, 0, 0); var yAxis = new Vector3(0, PositionMax.Y * WorldScale.Y - leftCorner.Y, 0); var zAxis = new Vector3(0, 0, PositionMax.Z * WorldScale.Z - leftCorner.Z); if (!WorldRotation.IsIdentity) { WorldRotation.Rotate(ref leftCorner); WorldRotation.Rotate(ref xAxis); WorldRotation.Rotate(ref yAxis); WorldRotation.Rotate(ref zAxis); } leftCorner += WorldPosition; var i = startIdx; if (Interpolate) { // Interpolate positions between the old and the new one var positionDistance = (hasBegun) ? oldPosition - WorldPosition : Vector3.Zero; oldPosition = WorldPosition; hasBegun = true; var totalCountLessOne = (startIdx < endIdx) ? (endIdx - startIdx - 1) : (endIdx - startIdx + maxCapacity - 1); var stepF = (totalCountLessOne > 1) ? (1f / totalCountLessOne) : 1f; var step = 0f; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandPos = leftCorner; particleRandPos += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandPos += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandPos += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); particleRandPos += positionDistance * step; step += stepF; (*((Vector3 *)particle[posField])) = particleRandPos; if (oldField.IsValid()) { (*((Vector3 *)particle[oldField])) = particleRandPos; } i = (i + 1) % maxCapacity; } } else { // Do not interpolate position while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandPos = leftCorner; particleRandPos += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandPos += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandPos += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); (*((Vector3 *)particle[posField])) = particleRandPos; if (oldField.IsValid()) { (*((Vector3 *)particle[oldField])) = particleRandPos; } i = (i + 1) % maxCapacity; } } }
public ParticleSorterDefault(ParticlePool pool) { ParticlePool = pool; }
public unsafe void ParticleGetSet(ParticlePool.ListPolicy policy) { const int maxParticles = 10; var pool = new ParticlePool(0, maxParticles, policy); // Spawn all particles for (var i = 0; i < maxParticles; i++) { pool.AddParticle(); } const bool forceCreation = true; pool.FieldExists(ParticleFields.Position, forceCreation); pool.FieldExists(ParticleFields.RemainingLife, forceCreation); pool.FieldExists(ParticleFields.Velocity, forceCreation); pool.FieldExists(ParticleFields.Size, forceCreation); var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); var vectorToSet = new Vector3(0, 0, 0); var scalarToSet = 0f; foreach (var particle in pool) { vectorToSet.Y = scalarToSet; *((Vector3*)particle[positionField]) = vectorToSet; *((float*)particle[lifetimeField]) = scalarToSet; *((Vector3*)particle[velocityField]) = vectorToSet; *((float*)particle[sizeField]) = scalarToSet; scalarToSet++; } // Assert the values are the same scalarToSet = 0f; foreach (var particle in pool) { Assert.That(*((Vector3*)particle[positionField]), Is.EqualTo(new Vector3(0, scalarToSet, 0))); Assert.That(*((float*)particle[lifetimeField]), Is.EqualTo(scalarToSet)); Assert.That(*((Vector3*)particle[velocityField]), Is.EqualTo(new Vector3(0, scalarToSet, 0))); Assert.That(*((float*)particle[sizeField]), Is.EqualTo(scalarToSet)); scalarToSet++; } // "Update" the values with delta time var dt = 0.033333f; foreach (var particle in pool) { var pos = ((Vector3*)particle[positionField]); var vel = ((Vector3*)particle[velocityField]); *pos += *vel * dt; *((float*)particle[lifetimeField]) += 1; } scalarToSet = 0f; foreach (var particle in pool) { Assert.That(*((Vector3*)particle[positionField]), Is.EqualTo(new Vector3(0, scalarToSet, 0) + *((Vector3*)particle[velocityField]) * dt)); Assert.That(*((float*)particle[lifetimeField]), Is.EqualTo(scalarToSet + 1)); scalarToSet++; } }
public void SetPool(ParticlePool p) { pool = p; }
/// <summary> /// The Update(...) step is called every frame for the particle pool, containing all particles /// Since this is a post-updater the Update(...) step is called *after* new particles have spawned for this frame /// Regular updaters are invoked *before* spawning new particles and avoid updating the particles on the frame they spawn /// </summary> public override void Update(float dt, ParticlePool pool) { // Make sure the fields we require exist, otherwise trying to access them will result in an invalid memory // The particle pool can check if the accessor is valid on each access, but that would be very inefficient so it skips the check if (!pool.FieldExists(ParticleFields.Life) || !pool.FieldExists(CustomParticleFields.RectangleXY)) { return; } var lifeField = pool.GetField(ParticleFields.Life); var rectField = pool.GetField(CustomParticleFields.RectangleXY); // Instead of switching for each particle, we switch only once and then batch-update the particles to improve performance switch (Curve) { // X and Y sides both depend on cos(time) case AnimatedCurveEnum.CosCos: { foreach (var particle in pool) { // Get the particle's remaining life. It's normalized between 0 and 1 var lifePi = particle.Get(lifeField) * MathUtil.Pi; // Set the rectangle as a simple function over time particle.Set(rectField, new Vector2((float)Math.Cos(lifePi), (float)Math.Cos(lifePi))); } } break; // X and Y sides both depend on sin(time) case AnimatedCurveEnum.SinSin: { foreach (var particle in pool) { // Get the particle's remaining life. It's normalized between 0 and 1 var lifePi = particle.Get(lifeField) * MathUtil.Pi; // Set the rectangle as a simple function over time particle.Set(rectField, new Vector2((float)Math.Sin(lifePi), (float)Math.Sin(lifePi))); } } break; // X and Y sides depend on cos(time) and sin(time) case AnimatedCurveEnum.CosSin: { foreach (var particle in pool) { // Get the particle's remaining life. It's normalized between 0 and 1 var lifePi = particle.Get(lifeField) * MathUtil.Pi; // Set the rectangle as a simple function over time particle.Set(rectField, new Vector2((float)Math.Cos(lifePi), (float)Math.Sin(lifePi))); } } break; // X and Y sides depend on sin(time) and cos(time) case AnimatedCurveEnum.SinCos: { foreach (var particle in pool) { // Get the particle's remaining life. It's normalized between 0 and 1 var lifePi = particle.Get(lifeField) * MathUtil.Pi; // Set the rectangle as a simple function over time particle.Set(rectField, new Vector2((float)Math.Sin(lifePi), (float)Math.Cos(lifePi))); } } break; } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.RandomSeed)) return; // Collect the total number of living particles in the parent pool which have a Position field var parentPool = Parent?.Pool; var parentParticlesCount = parentPool?.LivingParticles ?? 0; var posFieldParent = parentPool?.GetField(ParticleFields.Position) ?? ParticleFieldAccessor<Vector3>.Invalid(); if (!posFieldParent.IsValid()) { parentParticlesCount = 0; } var oldPosFieldParent = parentPool?.GetField(ParticleFields.OldPosition) ?? ParticleFieldAccessor<Vector3>.Invalid(); var spawnControlField = GetSpawnControlField(); var posField = pool.GetField(ParticleFields.Position); var rndField = pool.GetField(ParticleFields.RandomSeed); var oldField = pool.GetField(ParticleFields.OldPosition); var leftCorner = PositionMin * WorldScale; var xAxis = new Vector3(PositionMax.X * WorldScale.X - leftCorner.X, 0, 0); var yAxis = new Vector3(0, PositionMax.Y * WorldScale.Y - leftCorner.Y, 0); var zAxis = new Vector3(0, 0, PositionMax.Z * WorldScale.Z - leftCorner.Z); if (!WorldRotation.IsIdentity) { WorldRotation.Rotate(ref leftCorner); WorldRotation.Rotate(ref xAxis); WorldRotation.Rotate(ref yAxis); WorldRotation.Rotate(ref zAxis); } // Already inheriting from parent if (parentParticlesCount == 0) leftCorner += WorldPosition; var sequentialParentIndex = 0; var sequentialParentParticles = 0; var parentIndex = 0; // Interpolation - if parent particle has OldPosition field var stepF = 0f; var stepTotal = 0f; var positionDistance = new Vector3(0, 0, 0); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandPos = leftCorner; particleRandPos += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandPos += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandPos += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); if (parentParticlesCount > 0) { var parentParticlePosition = new Vector3(0, 0, 0); // Spawn is fixed - parent particles have spawned a very specific number of children each if (spawnControlField.IsValid()) { // Interpolation - if parent particle has OldPosition field while (sequentialParentParticles == 0) { // Early out - no more fixed number children. Rest of the particles (if any) are skipped intentionally if (sequentialParentIndex >= parentParticlesCount) return; parentIndex = sequentialParentIndex; var tempParentParticle = parentPool.FromIndex(parentIndex); sequentialParentIndex++; var childrenAttribute = (*((ParticleChildrenAttribute*)tempParentParticle[spawnControlField])); sequentialParentParticles = (int)childrenAttribute.ParticlesToEmit; if (oldPosFieldParent.IsValid()) { stepF = (sequentialParentParticles > 0) ? (1f/(float)sequentialParentParticles) : 1; stepTotal = 0f; positionDistance = ((*((Vector3*)tempParentParticle[oldPosFieldParent])) - (*((Vector3*)tempParentParticle[posFieldParent]))); } } sequentialParentParticles--; var parentParticle = parentPool.FromIndex(parentIndex); parentParticlePosition = (*((Vector3*)parentParticle[posFieldParent])); parentParticlePosition += positionDistance * stepTotal; stepTotal += stepF; } // Spawn is not fixed - pick a parent at random else { parentIndex = (int)(parentParticlesCount * randSeed.GetFloat(RandomOffset.Offset1A + ParentSeedOffset)); var parentParticle = parentPool.FromIndex(parentIndex); parentParticlePosition = (*((Vector3*)parentParticle[posFieldParent])); } // Convert from Local -> World space if needed if (Parent.SimulationSpace == EmitterSimulationSpace.Local) { WorldRotation.Rotate(ref parentParticlePosition); parentParticlePosition *= WorldScale.X; parentParticlePosition += WorldPosition; } particleRandPos += parentParticlePosition; } (*((Vector3*)particle[posField])) = particleRandPos; if (oldField.IsValid()) { (*((Vector3*)particle[oldField])) = particleRandPos; } i = (i + 1) % maxCapacity; } }
/// <summary> /// Updates the field by sampling a single value over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateSingleSampler(ParticlePool pool) { var angleField = pool.GetField(ParticleFields.Angle); var lifeField = pool.GetField(ParticleFields.Life); foreach (var particle in pool) { var life = 1f - (*((float*)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) (*((float*)particle[angleField])) = MathUtil.DegreesToRadians(SamplerMain.Evaluate(life)); } }
// TODO This will have to change at some point when we have better idea of how customizable the fields will be. // Ideally no field should get initialized twice public unsafe void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { // Position - defaults to World position of the emitter var posField = pool.GetField(ParticleFields.Position); if (posField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3*)particle[posField])) = WorldPosition; i = (i + 1) % maxCapacity; } } // Old position - defaults to World position of the emitter var oldPosField = pool.GetField(ParticleFields.OldPosition); if (oldPosField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3*)particle[oldPosField])) = WorldPosition; i = (i + 1) % maxCapacity; } } // Direction - defaults to (0, 0, 0) var dirField = pool.GetField(ParticleFields.Direction); if (dirField.IsValid()) { var zeroDirection = new Vector3(0, 0, 0); for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3*)particle[dirField])) = zeroDirection; i = (i + 1) % maxCapacity; } } // Quaternion rotation - defaults to World rotation of the emitter var quatField = pool.GetField(ParticleFields.Quaternion); if (quatField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Quaternion*)particle[quatField])) = WorldRotation; i = (i + 1) % maxCapacity; } } // Angular rotation - defaults to 0 var rotField = pool.GetField(ParticleFields.Rotation); if (rotField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((float*)particle[rotField])) = 0; i = (i + 1) % maxCapacity; } } // Velocity - defaults to (0, 0, 0) var velField = pool.GetField(ParticleFields.Velocity); if (velField.IsValid()) { var zeroVelocity = new Vector3(0, 0, 0); for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3*)particle[velField])) = zeroVelocity; i = (i + 1) % maxCapacity; } } // Size - defaults to the world scale of the emitter var sizeField = pool.GetField(ParticleFields.Size); if (sizeField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((float*)particle[sizeField])) = WorldScale; i = (i + 1) % maxCapacity; } } // Velocity - defaults to (0, 0, 0) var colField = pool.GetField(ParticleFields.Color); if (colField.IsValid()) { var whiteColor = new Color4(1,1,1,1); for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Color4*)particle[colField])) = whiteColor; i = (i + 1) % maxCapacity; } } // ChildrenFlags fields for (int j = 0; j < ParticleFields.ChildrenFlags.Length; j++) { var flagField = pool.GetField(ParticleFields.ChildrenFlags[j]); if (flagField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((uint*)particle[flagField])) = 0; i = (i + 1) % maxCapacity; } } } }
/// <summary> /// Updates the field by interpolating between two sampled values over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateDoubleSampler(ParticlePool pool) { var angleField = pool.GetField(ParticleFields.Angle); var lifeField = pool.GetField(ParticleFields.Life); var randField = pool.GetField(ParticleFields.RandomSeed); foreach (var particle in pool) { var life = 1f - (*((float*)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) var randSeed = particle.Get(randField); var lerp = randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); var angle1 = SamplerMain.Evaluate(life); var angle2 = SamplerOptional.Evaluate(life); (*((float*)particle[angleField])) = MathUtil.DegreesToRadians(angle1 + (angle2 - angle1) * lerp); } }
public ParticleSorterDefault(ParticlePool pool) : base(pool) { }
public ParticleSorterLiving(ParticlePool pool) : base(pool) { particleList = new SortedParticle[pool.ParticleCapacity]; currentLivingParticles = 0; }
/// <summary> /// Updates all particles in the <see cref="ParticlePool"/> using this updater /// </summary> /// <param name="dt">Delta time in seconds which has passed since the last update call</param> /// <param name="pool">The target <see cref="ParticlePool"/> which needs to be updated</param> public abstract void Update(float dt, ParticlePool pool);
public void Sorting() { var customFieldDesc = new ParticleFieldDescription <UInt32>("SomeField", 0); const int maxParticles = 4; var pool = new ParticlePool(0, maxParticles); const bool forceCreation = true; pool.FieldExists(ParticleFields.Position, forceCreation); // Force creation of the position field pool.FieldExists(ParticleFields.RemainingLife, forceCreation); // Force creation of the life field pool.FieldExists(customFieldDesc, forceCreation); // Force creation of the custom field we just declared // We can extract them before the tight loop on all living particles var posField = pool.GetField(ParticleFields.Position); var lifeField = pool.GetField(ParticleFields.RemainingLife); var customField = pool.GetField(customFieldDesc); // Ad 4 particles var particle1 = pool.AddParticle(); var particle2 = pool.AddParticle(); var particle3 = pool.AddParticle(); var particle4 = pool.AddParticle(); particle1.Set(customField, (uint)1); particle2.Set(customField, (uint)2); particle3.Set(customField, (uint)3); particle4.Set(customField, (uint)4); particle1.Set(lifeField, 0.4f); particle2.Set(lifeField, 0.8f); particle3.Set(lifeField, 0.2f); particle4.Set(lifeField, 0.6f); particle1.Set(posField, new Vector3(0, 0, 3)); particle2.Set(posField, new Vector3(0, 0, 9)); particle3.Set(posField, new Vector3(0, 0, 5)); particle4.Set(posField, new Vector3(0, 0, 1)); // Don't sort uint[] sortedNone = { 1, 2, 3, 4 }; // List of expected values { var i = 0; foreach (var particle in pool) { Assert.Equal(sortedNone[i++], particle.Get(customField)); } } // Sort by depth uint[] sortedDepth = { 4, 1, 3, 2 }; // List of expected values { var depthSorter = new ParticleSorterDepth(pool); var sortedList = depthSorter.GetSortedList(new Vector3(0, 0, 1)); var i = 0; foreach (var particle in sortedList) { Assert.Equal(sortedDepth[i++], particle.Get(customField)); } } // Sort by age uint[] sortedAge = { 2, 4, 1, 3 }; // List of expected values { var ageSorter = new ParticleSorterAge(pool); var sortedList = ageSorter.GetSortedList(new Vector3(0, 0, 1)); var i = 0; foreach (var particle in sortedList) { Assert.Equal(sortedAge[i++], particle.Get(customField)); } } }
protected ParticleSorterCustom(ParticlePool pool, ParticleFieldDescription <T> fieldDesc) { ParticlePool = pool; this.fieldDesc = fieldDesc; }
public unsafe void PoolCapacity(ParticlePool.ListPolicy policy) { const int maxParticles = 10; var pool = new ParticlePool(0, maxParticles, policy); const bool forceCreation = true; pool.FieldExists(ParticleFields.Position, forceCreation); pool.FieldExists(ParticleFields.RemainingLife, forceCreation); pool.FieldExists(ParticleFields.Velocity, forceCreation); pool.FieldExists(ParticleFields.Size, forceCreation); var testPos = new Vector3(1, 2, 3); var testVel = new Vector3(5, 6, 7); var testLife = 5f; var testSize = 4f; // Spawn all particles for (int i = 0; i < maxParticles; i++) { pool.AddParticle(); } { // Field accessors break every time there is a change in the pool, so we need to exract them every time // We can extract them before the tight loop on all living particles var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); foreach (var particle in pool) { *((Vector3*)particle[positionField]) = testPos; *((float*)particle[lifetimeField]) = testLife; *((Vector3*)particle[velocityField]) = testVel; *((float*)particle[sizeField]) = testSize; } } // Double the pool capacity and assert that the first half of particles still have the same fields pool.SetCapacity(2 * maxParticles); { // Field accessors break every time there is a change in the pool var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); var sorter = new ParticleSorterLiving(pool); sorter.Sort(); var i = 0; foreach (var particle in sorter) { Assert.That(*((Vector3*)particle[positionField]), Is.EqualTo(testPos)); Assert.That(*((float*)particle[lifetimeField]), Is.EqualTo(testLife)); Assert.That(*((Vector3*)particle[velocityField]), Is.EqualTo(testVel)); Assert.That(*((float*)particle[sizeField]), Is.EqualTo(testSize)); i++; } // Assert that the number of living particles is still maxParticles, not maxParticles x2 Assert.That(i, Is.EqualTo(maxParticles)); } // Halve the pool capacity from its original size. Now all the particles should still have the same fields pool.SetCapacity(maxParticles / 2); { // Field accessors break every time there is a change in the pool var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); var sorter = new ParticleSorterLiving(pool); sorter.Sort(); var i = 0; foreach (var particle in sorter) { Assert.That(*((Vector3*)particle[positionField]), Is.EqualTo(testPos)); Assert.That(*((float*)particle[lifetimeField]), Is.EqualTo(testLife)); Assert.That(*((Vector3*)particle[velocityField]), Is.EqualTo(testVel)); Assert.That(*((float*)particle[sizeField]), Is.EqualTo(testSize)); i++; } // Assert that the number of living particles is still maxParticles /2, not maxParticles x2 Assert.That(i, Is.EqualTo(maxParticles / 2)); } }
public ParticleSorterOrder(ParticlePool pool) : base(pool, ParticleFields.Order) { }
public void SetPool(ObjectPool objPool) { pool = (ParticlePool)objPool; }
// internal List<ParticleFieldDescription> RequiredFields = new List<ParticleFieldDescription>(ParticlePool.DefaultMaxFielsPerPool); /// <summary> /// Override Initialize if your module acts as an Initializer and change its type to Initializer /// </summary> /// <param name="pool">Particle pool to target</param> /// <param name="startIdx">Starting index (included from the array)</param> /// <param name="endIdx">End index (excluded from the array)</param> /// <param name="maxCapacity">Max pool capacity (loops after this point) so that it's possible for (endIdx < startIdx)</param> public abstract void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity);
/// <summary> /// Updates the field by interpolating between two sampled values over the particle's lifetime /// </summary> /// <param name="pool">Target <see cref="ParticlePool"/></param> private unsafe void UpdateDoubleSampler(ParticlePool pool) { var sizeField = pool.GetField(ParticleFields.Size); var lifeField = pool.GetField(ParticleFields.Life); var randField = pool.GetField(ParticleFields.RandomSeed); SamplerMain.UpdateChanges(); SamplerOptional.UpdateChanges(); foreach (var particle in pool) { var life = 1f - (*((float*)particle[lifeField])); // The Life field contains remaining life, so for sampling we take (1 - life) var randSeed = particle.Get(randField); var lerp = randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); var size1 = SamplerMain.Evaluate(life); var size2 = SamplerOptional.Evaluate(life); (*((float*)particle[sizeField])) = WorldScale.X * (size1 + (size2 - size1) * lerp); } }
public ParticleSorterAge(ParticlePool pool) : base(pool, ParticleFields.Life) { }
/// <summary> /// Prepares fields accessors before the /// </summary> /// <param name="pool"></param> public abstract void PrepareFromPool(ParticlePool pool);
// TODO This will have to change at some point when we have better idea of how customizable the fields will be. // Ideally no field should get initialized twice public unsafe void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { // Position - defaults to World position of the emitter var posField = pool.GetField(ParticleFields.Position); if (posField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3 *)particle[posField])) = WorldPosition; i = (i + 1) % maxCapacity; } } // Old position - defaults to World position of the emitter var oldPosField = pool.GetField(ParticleFields.OldPosition); if (oldPosField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3 *)particle[oldPosField])) = WorldPosition; i = (i + 1) % maxCapacity; } } // Direction - defaults to (0, 0, 0) var dirField = pool.GetField(ParticleFields.Direction); if (dirField.IsValid()) { var zeroDirection = new Vector3(0, 0, 0); for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3 *)particle[dirField])) = zeroDirection; i = (i + 1) % maxCapacity; } } // Quaternion rotation - defaults to World rotation of the emitter var quatField = pool.GetField(ParticleFields.Quaternion); if (quatField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Quaternion *)particle[quatField])) = WorldRotation; i = (i + 1) % maxCapacity; } } // Angular rotation - defaults to 0 var rotField = pool.GetField(ParticleFields.Rotation); if (rotField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((float *)particle[rotField])) = 0; i = (i + 1) % maxCapacity; } } // Velocity - defaults to (0, 0, 0) var velField = pool.GetField(ParticleFields.Velocity); if (velField.IsValid()) { var zeroVelocity = new Vector3(0, 0, 0); for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Vector3 *)particle[velField])) = zeroVelocity; i = (i + 1) % maxCapacity; } } // Size - defaults to the world scale of the emitter var sizeField = pool.GetField(ParticleFields.Size); if (sizeField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((float *)particle[sizeField])) = WorldScale; i = (i + 1) % maxCapacity; } } // Velocity - defaults to (0, 0, 0) var colField = pool.GetField(ParticleFields.Color); if (colField.IsValid()) { var whiteColor = new Color4(1, 1, 1, 1); for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((Color4 *)particle[colField])) = whiteColor; i = (i + 1) % maxCapacity; } } // ChildrenFlags fields for (int j = 0; j < ParticleFields.ChildrenFlags.Length; j++) { var flagField = pool.GetField(ParticleFields.ChildrenFlags[j]); if (flagField.IsValid()) { for (var i = startIdx; i != endIdx;) { var particle = pool.FromIndex(i); (*((uint *)particle[flagField])) = 0; i = (i + 1) % maxCapacity; } } } }
private void Awake() { Instance = this; GrowPool(); }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Velocity) || !pool.FieldExists(ParticleFields.RandomSeed)) { return; } // Collect the total number of living particles in the parent pool which have a Velocity field var parentPool = Parent?.Pool; var parentParticlesCount = parentPool?.LivingParticles ?? 0; var velFieldParent = parentPool?.GetField(ParticleFields.Velocity) ?? ParticleFieldAccessor <Vector3> .Invalid(); if (!velFieldParent.IsValid()) { parentParticlesCount = 0; } var spawnControlField = GetSpawnControlField(); var velField = pool.GetField(ParticleFields.Velocity); var rndField = pool.GetField(ParticleFields.RandomSeed); var leftCorner = VelocityMin * WorldScale; var xAxis = new Vector3(VelocityMax.X * WorldScale.X - leftCorner.X, 0, 0); var yAxis = new Vector3(0, VelocityMax.Y * WorldScale.Y - leftCorner.Y, 0); var zAxis = new Vector3(0, 0, VelocityMax.Z * WorldScale.Z - leftCorner.Z); if (!WorldRotation.IsIdentity) { WorldRotation.Rotate(ref leftCorner); WorldRotation.Rotate(ref xAxis); WorldRotation.Rotate(ref yAxis); WorldRotation.Rotate(ref zAxis); } var sequentialParentIndex = 0; var sequentialParentParticles = 0; var parentIndex = 0; var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandVel = leftCorner; particleRandVel += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandVel += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandVel += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); if (parentParticlesCount > 0) { var parentParticleVelocity = new Vector3(0, 0, 0); // Spawn is fixed - parent particles have spawned a very specific number of children each if (spawnControlField.IsValid()) { while (sequentialParentParticles == 0) { // Early out - no more fixed number children. Rest of the particles (if any) are skipped intentionally if (sequentialParentIndex >= parentParticlesCount) { return; } parentIndex = sequentialParentIndex; var tempParentParticle = parentPool.FromIndex(parentIndex); sequentialParentIndex++; var childrenAttribute = (*((ParticleChildrenAttribute *)tempParentParticle[spawnControlField])); sequentialParentParticles = (int)childrenAttribute.ParticlesToEmit; } sequentialParentParticles--; var parentParticle = parentPool.FromIndex(parentIndex); parentParticleVelocity = (*((Vector3 *)parentParticle[velFieldParent])); } // Spawn is not fixed - pick a parent at random else { parentIndex = (int)(parentParticlesCount * randSeed.GetFloat(RandomOffset.Offset1A + ParentSeedOffset)); var parentParticle = parentPool.FromIndex(parentIndex); parentParticleVelocity = (*((Vector3 *)parentParticle[velFieldParent])); } // Convert from Local -> World space if needed if (Parent.SimulationSpace == EmitterSimulationSpace.Local) { WorldRotation.Rotate(ref parentParticleVelocity); parentParticleVelocity *= WorldScale.X; } particleRandVel += parentParticleVelocity * ParentVelocityFactor; } (*((Vector3 *)particle[velField])) = particleRandVel; i = (i + 1) % maxCapacity; } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var posField = pool.GetField(ParticleFields.Position); var oldField = pool.GetField(ParticleFields.OldPosition); var rndField = pool.GetField(ParticleFields.RandomSeed); var leftCorner = PositionMin * WorldScale; var xAxis = new Vector3(PositionMax.X * WorldScale.X - leftCorner.X, 0, 0); var yAxis = new Vector3(0, PositionMax.Y * WorldScale.Y - leftCorner.Y, 0); var zAxis = new Vector3(0, 0, PositionMax.Z * WorldScale.Z - leftCorner.Z); if (!WorldRotation.IsIdentity) { WorldRotation.Rotate(ref leftCorner); WorldRotation.Rotate(ref xAxis); WorldRotation.Rotate(ref yAxis); WorldRotation.Rotate(ref zAxis); } leftCorner += WorldPosition; var i = startIdx; if (Interpolate) { // Interpolate positions between the old and the new one var positionDistance = (hasBegun) ? oldPosition - WorldPosition : new Vector3(0, 0, 0); oldPosition = WorldPosition; hasBegun = true; var totalCountLessOne = (startIdx < endIdx) ? (endIdx - startIdx - 1) : (endIdx - startIdx + maxCapacity - 1); var stepF = (totalCountLessOne > 1) ? (1f/totalCountLessOne) : 1f; var step = 0f; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandPos = leftCorner; particleRandPos += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandPos += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandPos += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); particleRandPos += positionDistance * step; step += stepF; (*((Vector3*)particle[posField])) = particleRandPos; if (oldField.IsValid()) { (*((Vector3*)particle[oldField])) = particleRandPos; } i = (i + 1) % maxCapacity; } } else { // Do not interpolate position while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandPos = leftCorner; particleRandPos += xAxis*randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandPos += yAxis*randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandPos += zAxis*randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); (*((Vector3*)particle[posField])) = particleRandPos; if (oldField.IsValid()) { (*((Vector3*)particle[oldField])) = particleRandPos; } i = (i + 1)%maxCapacity; } } }
public void Sorting() { var customFieldDesc = new ParticleFieldDescription<UInt32>("SomeField", 0); const int maxParticles = 4; var pool = new ParticlePool(0, maxParticles); const bool forceCreation = true; pool.FieldExists(ParticleFields.Position, forceCreation); // Force creation of the position field pool.FieldExists(ParticleFields.RemainingLife, forceCreation); // Force creation of the life field pool.FieldExists(customFieldDesc, forceCreation); // Force creation of the custom field we just declared // We can extract them before the tight loop on all living particles var posField = pool.GetField(ParticleFields.Position); var lifeField = pool.GetField(ParticleFields.RemainingLife); var customField = pool.GetField(customFieldDesc); // Ad 4 particles var particle1 = pool.AddParticle(); var particle2 = pool.AddParticle(); var particle3 = pool.AddParticle(); var particle4 = pool.AddParticle(); particle1.Set(customField, (uint)1); particle2.Set(customField, (uint)2); particle3.Set(customField, (uint)3); particle4.Set(customField, (uint)4); particle1.Set(lifeField, 0.4f); particle2.Set(lifeField, 0.8f); particle3.Set(lifeField, 0.2f); particle4.Set(lifeField, 0.6f); particle1.Set(posField, new Vector3(0, 0, 3)); particle2.Set(posField, new Vector3(0, 0, 9)); particle3.Set(posField, new Vector3(0, 0, 5)); particle4.Set(posField, new Vector3(0, 0, 1)); // Don't sort uint[] sortedNone = { 1, 2, 3, 4 }; // List of expected values { var i = 0; foreach (var particle in pool) { Assert.That(particle.Get(customField), Is.EqualTo(sortedNone[i++])); } } // Sort by depth uint[] sortedDepth = { 4, 1, 3, 2 }; // List of expected values { GetSortIndex<Vector3> sortByDepth = value => value.Z; var depthSorter = new ParticleSorterCustom<Vector3>(pool, ParticleFields.Position, sortByDepth); depthSorter.Sort(); var i = 0; foreach (var particle in depthSorter) { Assert.That(particle.Get(customField), Is.EqualTo(sortedDepth[i++])); } } // Sort by age uint[] sortedAge = { 3, 1, 4, 2 }; // List of expected values { GetSortIndex<float> sortByAge = value => { return value; }; var ageSorter = new ParticleSorterCustom<float>(pool, ParticleFields.Life, sortByAge); ageSorter.Sort(); var i = 0; foreach (var particle in ageSorter) { Assert.That(particle.Get(customField), Is.EqualTo(sortedAge[i++])); } } }
public void Render(GLEx g, float x, float y) { if (!visible) { return; } if ((sprite == null) && (defaultImageName != null)) { LoadSystemParticleImage(); } g.Translate(x, y); if (blendingMode == BLEND_ADDITIVE) { //GLEx.self.setBlendMode(GL.MODE_ALPHA_ONE); } if (UsePoints()) { //GLEx.gl10.glEnable(GL.GL_POINT_SMOOTH); //g.glTex2DDisable(); } for (int emitterIdx = 0; emitterIdx < emitters.Count; emitterIdx++) { ParticleEmitter emitter = emitters[emitterIdx]; if (!emitter.IsEnabled()) { continue; } if (emitter.UseAdditive()) { //g.setBlendMode(GL.MODE_ALPHA_ONE); } ParticlePool pool = particlesByEmitter[emitter]; LTexture image = emitter.GetImage(); if (image == null) { image = this.sprite; } if (!emitter.IsOriented() && !emitter.UsePoints(this)) { image.GLBegin(); } for (int i = 0; i < pool.particles.Length; i++) { if (pool.particles[i].InUse()) { pool.particles[i].Render(); } } if (!emitter.IsOriented() && !emitter.UsePoints(this)) { image.GLEnd(); } if (emitter.UseAdditive()) { //g.setBlendMode(GL.MODE_NORMAL); } } if (UsePoints()) { //GLEx.gl10.glDisable(GL.GL_POINT_SMOOTH); } if (blendingMode == BLEND_ADDITIVE) { //g.setBlendMode(GL.MODE_NORMAL); } g.ResetColor(); g.Translate(-x, -y); }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Size) || !pool.FieldExists(ParticleFields.RandomSeed)) { return; } var parentPool = Parent?.Pool; var parentParticlesCount = parentPool?.LivingParticles ?? 0; var sizeFieldParent = parentPool?.GetField(ParticleFields.Size) ?? ParticleFieldAccessor <float> .Invalid(); if (!sizeFieldParent.IsValid()) { parentParticlesCount = 0; } var spawnControlField = GetSpawnControlField(); var sizeField = pool.GetField(ParticleFields.Size); var rndField = pool.GetField(ParticleFields.RandomSeed); var minSize = WorldScale.X * RandomSize.X; var sizeGap = WorldScale.X * RandomSize.Y - minSize; var sequentialParentIndex = 0; var sequentialParentParticles = 0; var parentIndex = 0; var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandomSize = minSize + sizeGap * randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); if (parentParticlesCount > 0) { var parentParticleSize = 1f; // Spawn is fixed - parent particles have spawned a very specific number of children each if (spawnControlField.IsValid()) { while (sequentialParentParticles == 0) { // Early out - no more fixed number children. Rest of the particles (if any) are skipped intentionally if (sequentialParentIndex >= parentParticlesCount) { return; } parentIndex = sequentialParentIndex; var tempParentParticle = parentPool.FromIndex(parentIndex); sequentialParentIndex++; var childrenAttribute = (*((ParticleChildrenAttribute *)tempParentParticle[spawnControlField])); sequentialParentParticles = (int)childrenAttribute.ParticlesToEmit; } sequentialParentParticles--; var parentParticle = parentPool.FromIndex(parentIndex); parentParticleSize = (*((float *)parentParticle[sizeFieldParent])); } // Spawn is not fixed - pick a parent at random else { parentIndex = (int)(parentParticlesCount * randSeed.GetFloat(RandomOffset.Offset1A + ParentSeedOffset)); var parentParticle = parentPool.FromIndex(parentIndex); parentParticleSize = (*((float *)parentParticle[sizeFieldParent])); } // Convert from Local -> World space if needed if (Parent.SimulationSpace == EmitterSimulationSpace.Local) { parentParticleSize *= WorldScale.X; } particleRandomSize *= parentParticleSize; } (*((float *)particle[sizeField])) = particleRandomSize; i = (i + 1) % maxCapacity; } }
private void Awake() { instance = this; Setup(); }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Position) || !pool.FieldExists(ParticleFields.RandomSeed)) { return; } // Collect the total number of living particles in the parent pool which have a Position field var parentPool = Parent?.Pool; var parentParticlesCount = parentPool?.LivingParticles ?? 0; var posFieldParent = parentPool?.GetField(ParticleFields.Position) ?? ParticleFieldAccessor <Vector3> .Invalid(); if (!posFieldParent.IsValid()) { parentParticlesCount = 0; } var oldPosFieldParent = parentPool?.GetField(ParticleFields.OldPosition) ?? ParticleFieldAccessor <Vector3> .Invalid(); var spawnControlField = GetSpawnControlField(); var posField = pool.GetField(ParticleFields.Position); var rndField = pool.GetField(ParticleFields.RandomSeed); var oldField = pool.GetField(ParticleFields.OldPosition); var leftCorner = PositionMin * WorldScale; var xAxis = new Vector3(PositionMax.X * WorldScale.X - leftCorner.X, 0, 0); var yAxis = new Vector3(0, PositionMax.Y * WorldScale.Y - leftCorner.Y, 0); var zAxis = new Vector3(0, 0, PositionMax.Z * WorldScale.Z - leftCorner.Z); if (!WorldRotation.IsIdentity) { WorldRotation.Rotate(ref leftCorner); WorldRotation.Rotate(ref xAxis); WorldRotation.Rotate(ref yAxis); WorldRotation.Rotate(ref zAxis); } // Already inheriting from parent if (parentParticlesCount == 0) { leftCorner += WorldPosition; } var sequentialParentIndex = 0; var sequentialParentParticles = 0; var parentIndex = 0; // Interpolation - if parent particle has OldPosition field var stepF = 0f; var stepTotal = 0f; var positionDistance = new Vector3(0, 0, 0); var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandPos = leftCorner; particleRandPos += xAxis * randSeed.GetFloat(RandomOffset.Offset3A + SeedOffset); particleRandPos += yAxis * randSeed.GetFloat(RandomOffset.Offset3B + SeedOffset); particleRandPos += zAxis * randSeed.GetFloat(RandomOffset.Offset3C + SeedOffset); if (parentParticlesCount > 0) { var parentParticlePosition = new Vector3(0, 0, 0); // Spawn is fixed - parent particles have spawned a very specific number of children each if (spawnControlField.IsValid()) { // Interpolation - if parent particle has OldPosition field while (sequentialParentParticles == 0) { // Early out - no more fixed number children. Rest of the particles (if any) are skipped intentionally if (sequentialParentIndex >= parentParticlesCount) { return; } parentIndex = sequentialParentIndex; var tempParentParticle = parentPool.FromIndex(parentIndex); sequentialParentIndex++; var childrenAttribute = (*((ParticleChildrenAttribute *)tempParentParticle[spawnControlField])); sequentialParentParticles = (int)childrenAttribute.ParticlesToEmit; if (oldPosFieldParent.IsValid()) { stepF = (sequentialParentParticles > 0) ? (1f / (float)sequentialParentParticles) : 1; stepTotal = 0f; positionDistance = ((*((Vector3 *)tempParentParticle[oldPosFieldParent])) - (*((Vector3 *)tempParentParticle[posFieldParent]))); } } sequentialParentParticles--; var parentParticle = parentPool.FromIndex(parentIndex); parentParticlePosition = (*((Vector3 *)parentParticle[posFieldParent])); parentParticlePosition += positionDistance * stepTotal; stepTotal += stepF; } // Spawn is not fixed - pick a parent at random else { parentIndex = (int)(parentParticlesCount * randSeed.GetFloat(RandomOffset.Offset1A + ParentSeedOffset)); var parentParticle = parentPool.FromIndex(parentIndex); parentParticlePosition = (*((Vector3 *)parentParticle[posFieldParent])); } // Convert from Local -> World space if needed if (Parent.SimulationSpace == EmitterSimulationSpace.Local) { WorldRotation.Rotate(ref parentParticlePosition); parentParticlePosition *= WorldScale.X; parentParticlePosition += WorldPosition; } particleRandPos += parentParticlePosition; } (*((Vector3 *)particle[posField])) = particleRandPos; if (oldField.IsValid()) { (*((Vector3 *)particle[oldField])) = particleRandPos; } i = (i + 1) % maxCapacity; } }
/// <inheritdoc /> public unsafe override void Initialize(ParticlePool pool, int startIdx, int endIdx, int maxCapacity) { if (!pool.FieldExists(ParticleFields.Size) || !pool.FieldExists(ParticleFields.RandomSeed)) return; var parentPool = Parent?.Pool; var parentParticlesCount = parentPool?.LivingParticles ?? 0; var sizeFieldParent = parentPool?.GetField(ParticleFields.Size) ?? ParticleFieldAccessor<float>.Invalid(); if (!sizeFieldParent.IsValid()) { parentParticlesCount = 0; } var spawnControlField = GetSpawnControlField(); var sizeField = pool.GetField(ParticleFields.Size); var rndField = pool.GetField(ParticleFields.RandomSeed); var minSize = WorldScale.X * RandomSize.X; var sizeGap = WorldScale.X * RandomSize.Y - minSize; var sequentialParentIndex = 0; var sequentialParentParticles = 0; var parentIndex = 0; var i = startIdx; while (i != endIdx) { var particle = pool.FromIndex(i); var randSeed = particle.Get(rndField); var particleRandomSize = minSize + sizeGap * randSeed.GetFloat(RandomOffset.Offset1A + SeedOffset); if (parentParticlesCount > 0) { var parentParticleSize = 1f; // Spawn is fixed - parent particles have spawned a very specific number of children each if (spawnControlField.IsValid()) { while (sequentialParentParticles == 0) { // Early out - no more fixed number children. Rest of the particles (if any) are skipped intentionally if (sequentialParentIndex >= parentParticlesCount) return; parentIndex = sequentialParentIndex; var tempParentParticle = parentPool.FromIndex(parentIndex); sequentialParentIndex++; var childrenAttribute = (*((ParticleChildrenAttribute*)tempParentParticle[spawnControlField])); sequentialParentParticles = (int)childrenAttribute.ParticlesToEmit; } sequentialParentParticles--; var parentParticle = parentPool.FromIndex(parentIndex); parentParticleSize = (*((float*)parentParticle[sizeFieldParent])); } // Spawn is not fixed - pick a parent at random else { parentIndex = (int)(parentParticlesCount * randSeed.GetFloat(RandomOffset.Offset1A + ParentSeedOffset)); var parentParticle = parentPool.FromIndex(parentIndex); parentParticleSize = (*((float*)parentParticle[sizeFieldParent])); } // Convert from Local -> World space if needed if (Parent.SimulationSpace == EmitterSimulationSpace.Local) { parentParticleSize *= WorldScale.X; } particleRandomSize *= parentParticleSize; } (*((float*)particle[sizeField])) = particleRandomSize; i = (i + 1) % maxCapacity; } }
public ParticleList(ParticlePool pool, int capacity, SortedParticle[] list = null) { this.pool = pool; this.listCapacity = capacity; this.sortedList = list; }
public unsafe void PoolCapacity(ParticlePool.ListPolicy policy) { const int maxParticles = 10; var pool = new ParticlePool(0, maxParticles, policy); const bool forceCreation = true; pool.FieldExists(ParticleFields.Position, forceCreation); pool.FieldExists(ParticleFields.RemainingLife, forceCreation); pool.FieldExists(ParticleFields.Velocity, forceCreation); pool.FieldExists(ParticleFields.Size, forceCreation); var testPos = new Vector3(1, 2, 3); var testVel = new Vector3(5, 6, 7); var testLife = 5f; var testSize = 4f; // Spawn all particles for (int i = 0; i < maxParticles; i++) { pool.AddParticle(); } { // Field accessors break every time there is a change in the pool, so we need to exract them every time // We can extract them before the tight loop on all living particles var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); foreach (var particle in pool) { *((Vector3 *)particle[positionField]) = testPos; *((float *)particle[lifetimeField]) = testLife; *((Vector3 *)particle[velocityField]) = testVel; *((float *)particle[sizeField]) = testSize; } } // Double the pool capacity and assert that the first half of particles still have the same fields pool.SetCapacity(2 * maxParticles); { // Field accessors break every time there is a change in the pool var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); var sorter = new ParticleSorterLiving(pool); var sortedList = sorter.GetSortedList(new Vector3(0, 0, -1)); var i = 0; foreach (var particle in sortedList) { Assert.Equal(testPos, *((Vector3 *)particle[positionField])); Assert.Equal(testLife, *((float *)particle[lifetimeField])); Assert.Equal(testVel, *((Vector3 *)particle[velocityField])); Assert.Equal(testSize, *((float *)particle[sizeField])); i++; } sorter.FreeSortedList(ref sortedList); // Assert that the number of living particles is still maxParticles, not maxParticles x2 Assert.Equal(maxParticles, i); } // Halve the pool capacity from its original size. Now all the particles should still have the same fields pool.SetCapacity(maxParticles / 2); { // Field accessors break every time there is a change in the pool var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); var sorter = new ParticleSorterLiving(pool); var sortedList = sorter.GetSortedList(new Vector3(0, 0, -1)); var i = 0; foreach (var particle in sortedList) { Assert.Equal(testPos, *((Vector3 *)particle[positionField])); Assert.Equal(testLife, *((float *)particle[lifetimeField])); Assert.Equal(testVel, *((Vector3 *)particle[velocityField])); Assert.Equal(testSize, *((float *)particle[sizeField])); i++; } sorter.FreeSortedList(ref sortedList); // Assert that the number of living particles is still maxParticles /2, not maxParticles x2 Assert.Equal(maxParticles / 2, i); } }
/// <summary> /// Default constructor. Initializes the pool and all collections contained in the <see cref="ParticleEmitter"/> /// </summary> public ParticleEmitter() { pool = new ParticlePool(0, 0); PoolChangedNotification(); requiredFields = new Dictionary<ParticleFieldDescription, int>(); // For now all particles require Life and RandomSeed fields, always AddRequiredField(ParticleFields.RemainingLife); AddRequiredField(ParticleFields.RandomSeed); AddRequiredField(ParticleFields.Position); initialDefaultFields = new InitialDefaultFields(); Initializers = new FastTrackingCollection<ParticleInitializer>(); Initializers.CollectionChanged += ModulesChanged; Updaters = new FastTrackingCollection<ParticleUpdater>(); Updaters.CollectionChanged += ModulesChanged; Spawners = new FastTrackingCollection<ParticleSpawner>(); Spawners.CollectionChanged += SpawnersChanged; }
public unsafe void ParticleGetSet(ParticlePool.ListPolicy policy) { const int maxParticles = 10; var pool = new ParticlePool(0, maxParticles, policy); // Spawn all particles for (var i = 0; i < maxParticles; i++) { pool.AddParticle(); } const bool forceCreation = true; pool.FieldExists(ParticleFields.Position, forceCreation); pool.FieldExists(ParticleFields.RemainingLife, forceCreation); pool.FieldExists(ParticleFields.Velocity, forceCreation); pool.FieldExists(ParticleFields.Size, forceCreation); var positionField = pool.GetField(ParticleFields.Position); var lifetimeField = pool.GetField(ParticleFields.RemainingLife); var velocityField = pool.GetField(ParticleFields.Velocity); var sizeField = pool.GetField(ParticleFields.Size); var vectorToSet = new Vector3(0, 0, 0); var scalarToSet = 0f; foreach (var particle in pool) { vectorToSet.Y = scalarToSet; *((Vector3 *)particle[positionField]) = vectorToSet; *((float *)particle[lifetimeField]) = scalarToSet; *((Vector3 *)particle[velocityField]) = vectorToSet; *((float *)particle[sizeField]) = scalarToSet; scalarToSet++; } // Assert the values are the same scalarToSet = 0f; foreach (var particle in pool) { Assert.Equal(new Vector3(0, scalarToSet, 0), *((Vector3 *)particle[positionField])); Assert.Equal(scalarToSet, *((float *)particle[lifetimeField])); Assert.Equal(new Vector3(0, scalarToSet, 0), *((Vector3 *)particle[velocityField])); Assert.Equal(scalarToSet, *((float *)particle[sizeField])); scalarToSet++; } // "Update" the values with delta time var dt = 0.033333f; foreach (var particle in pool) { var pos = ((Vector3 *)particle[positionField]); var vel = ((Vector3 *)particle[velocityField]); *pos += *vel * dt; *((float *)particle[lifetimeField]) += 1; } scalarToSet = 0f; foreach (var particle in pool) { Assert.Equal(*((Vector3 *)particle[positionField]), new Vector3(0, scalarToSet, 0) + *((Vector3 *)particle[velocityField]) * dt); Assert.Equal(scalarToSet + 1, *((float *)particle[lifetimeField])); scalarToSet++; } }