/// <summary> /// Essentially wrapper for effectParticle() with some masking logic for advanced particle-effector /// relantionships /// /// Override with caution. /// </summary> public void simulateParticleEffect(SSParticle particle, float deltaT) { if (effectorMaskCheck(particle.effectorMask)) { effectParticle(particle, deltaT); } }
protected virtual void readParticle(int idx, SSParticle p) { p.life = _lives [idx]; p.pos = _readElement(_positions, idx).Value; p.orientation.Xy = _readElement(_orientationsXY, idx).Value; p.orientation.Z = _readElement(_orientationsZ, idx).Value; p.viewDepth = _readElement(_viewDepths, idx); if (p.life <= 0f) { return; // the rest does not matter } p.masterScale = _readElement(_masterScales, idx).Value; p.componentScale.Xy = _readElement(_componentScalesXY, idx).Value; p.componentScale.Z = _readElement(_componentScalesZ, idx).Value; p.color = Color4Helper.FromUInt32(_readElement(_colors, idx).Color); //p.SpriteIndex = _readElement(m_spriteIndices, idx).Value; p.spriteRect.X = _readElement(_spriteOffsetsU, idx).Value; p.spriteRect.Y = _readElement(_spriteOffsetsV, idx).Value; p.spriteRect.Width = _readElement(_spriteSizesU, idx).Value; p.spriteRect.Height = _readElement(_spriteSizesV, idx).Value; p.vel = _readElement(_velocities, idx); p.angularVelocity = _readElement(_angularVelocities, idx); p.mass = _readElement(_masses, idx); p.rotationalInnertia = _readElement(_rotationalInnertias, idx); p.drag = _readElement(_drags, idx); p.rotationalDrag = _readElement(_rotationalDrags, idx); p.effectorMask = (ushort)((int)_readElement(_effectorMasksHigh, idx) << 8 | (int)_readElement(_effectorMasksLow, idx)); }
public IInterpolater[] interpolaters = { new LinearInterpolater() }; // default to using LERP for everything protected override sealed void effectParticle(SSParticle particle, float deltaT) { _timeSinceReset += deltaT; float timeElapsed = float.IsNaN(particleLifetime) ? _timeSinceReset : (1f - particle.life / particleLifetime); float lastKey = keyframes.Keys [keyframes.Count - 1]; if (timeElapsed > lastKey) { applyValue(particle, keyframes [lastKey]); } else { float prevKey = keyframes.Keys [0]; for (int i = 1; i < keyframes.Keys.Count; ++i) { float key = keyframes.Keys [i]; if (timeElapsed < key) { IInterpolater interpolater = i < interpolaters.Length ? interpolaters [i] : interpolaters[interpolaters.Length - 1]; float progression = (timeElapsed - prevKey) / (key - prevKey); T value = computeValue(interpolater, keyframes [prevKey], keyframes [key], progression); applyValue(particle, value); break; } prevKey = key; } } }
protected virtual void particleSwap(int leftIdx, int rightIdx) { if (leftIdx == rightIdx) { return; } // TODO Consider swaping on a per component basis. // It may have better peformance // But adds more per-component maintenance SSParticle leftParticle = createNewParticle(); SSParticle rightParticle = createNewParticle(); readParticle(leftIdx, leftParticle); readParticle(rightIdx, rightParticle); // write in reverse writeParticle(leftIdx, rightParticle); writeParticle(rightIdx, leftParticle); #if true if (_nextIdxToOverwrite == leftIdx) { _nextIdxToOverwrite = rightIdx; } else if (_nextIdxToOverwrite == rightIdx) { _nextIdxToOverwrite = leftIdx; } #endif }
protected virtual void writeParticle(int idx, SSParticle p) { _lives [idx] = p.life; writeDataIfNeeded(ref _positions, idx, new SSAttributeVec3(p.pos)); writeDataIfNeeded(ref _orientationsXY, idx, new SSAttributeVec2(p.orientation.Xy)); writeDataIfNeeded(ref _orientationsZ, idx, new SSAttributeFloat(p.orientation.Z)); writeDataIfNeeded(ref _masterScales, idx, new SSAttributeFloat(p.masterScale)); writeDataIfNeeded(ref _componentScalesXY, idx, new SSAttributeVec2(p.componentScale.Xy)); writeDataIfNeeded(ref _componentScalesZ, idx, new SSAttributeFloat(p.componentScale.Z)); writeDataIfNeeded(ref _colors, idx, new SSAttributeColor(Color4Helper.ToUInt32(p.color))); //writeDataIfNeeded(ref m_spriteIndices, idx, new SSAttributeByte(p.SpriteIndex)); writeDataIfNeeded(ref _spriteOffsetsU, idx, new SSAttributeFloat(p.spriteRect.X)); writeDataIfNeeded(ref _spriteOffsetsV, idx, new SSAttributeFloat(p.spriteRect.Y)); writeDataIfNeeded(ref _spriteSizesU, idx, new SSAttributeFloat(p.spriteRect.Width)); writeDataIfNeeded(ref _spriteSizesV, idx, new SSAttributeFloat(p.spriteRect.Height)); writeDataIfNeeded(ref _velocities, idx, p.vel); writeDataIfNeeded(ref _angularVelocities, idx, p.angularVelocity); writeDataIfNeeded(ref _masses, idx, p.mass); writeDataIfNeeded(ref _rotationalInnertias, idx, p.rotationalInnertia); writeDataIfNeeded(ref _drags, idx, p.drag); writeDataIfNeeded(ref _rotationalDrags, idx, p.rotationalDrag); writeDataIfNeeded(ref _viewDepths, idx, p.viewDepth); writeDataIfNeeded(ref _effectorMasksHigh, idx, (byte)((p.effectorMask & 0xFF00) >> 8)); writeDataIfNeeded(ref _effectorMasksLow, idx, (byte)(p.effectorMask & 0xFF)); }
protected override void writeParticle(int idx, SSParticle p) { #if false // attempt of only writing things that are relevant; skipping the rest _lives [idx] = p.life; writeDataIfNeeded(ref _positions, idx, new SSAttributeVec3(p.pos)); writeDataIfNeeded(ref _viewDepths, idx, p.viewDepth); writeDataIfNeeded(ref _effectorMasksHigh, idx, (ushort)((p.effectorMask & 0xFF00) >> 8)); writeDataIfNeeded(ref _effectorMasksLow, idx, (ushort)(p.effectorMask & 0xFF)); writeDataIfNeeded(ref _colors, idx, new SSAttributeColor(Color4Helper.ToUInt32(p.color))); #else // for some reason the above doesn't work, so lets just do things slightly less efficiently for now base.writeParticle(idx, p); #endif var ts = (STrailsSegment)p; var innerColor = Color4Helper.ToUInt32(ts.cylInnerColor); writeDataIfNeeded(ref _cylInnerColors, idx, new SSAttributeColor(innerColor)); writeDataIfNeeded(ref _innerColorRatios, idx, new SSAttributeFloat(ts.innerColorRatio)); writeDataIfNeeded(ref _outerColorRatios, idx, new SSAttributeFloat(ts.outerColorRatio)); writeDataIfNeeded(ref _cylAxes, idx, new SSAttributeVec3(ts.cylAxis)); writeDataIfNeeded(ref _prevJointAxes, idx, new SSAttributeVec3(ts.prevJointAxis)); writeDataIfNeeded(ref _nextJointAxes, idx, new SSAttributeVec3(ts.nextJointAxis)); writeDataIfNeeded(ref _cylLengths, idx, new SSAttributeFloat(ts.cylLendth)); writeDataIfNeeded(ref _cylWidths, idx, new SSAttributeFloat(ts.cylWidth)); writeDataIfNeeded(ref _nextSegmentData, idx, ts.nextSegmentIdx); writeDataIfNeeded(ref _prevSegmentData, idx, ts.prevSegmentIdx); }
protected override void configureNewParticle(SSParticle p) { base.configureNewParticle(p); float r = Interpolate.Lerp(radiusOffsetMin, radiusOffsetMax, nextFloat()); float theta = Interpolate.Lerp(thetaMin, thetaMax, nextFloat()); float phi = Interpolate.Lerp(phiMin, phiMax, nextFloat()); float xy = (float)Math.Cos(phi); float x = xy * (float)Math.Cos(theta); float y = xy * (float)Math.Sin(theta); float z = (float)Math.Sin(phi); Vector3 xAxis, yAxis; OpenTKHelper.TwoPerpAxes(up, out xAxis, out yAxis); Vector3 xyz = x * xAxis + y * yAxis + z * up; p.pos = center + r * xyz; float velocityMag = Interpolate.Lerp(velocityFromCenterMagnitudeMin, velocityFromCenterMagnitudeMax, nextFloat()); p.vel += velocityMag * xyz; if (orientAwayFromCenter) { p.orientation.Z = -theta; p.orientation.Y = phi; } //Console.WriteLine("particle emitted with vel = " + p.vel.Length); }
protected override void applyValue(SSParticle particle, Color4 value) { value.R *= colorMask.R; value.G *= colorMask.G; value.B *= colorMask.B; value.A *= colorMask.A; particle.color = value; }
protected override sealed void effectParticle(SSParticle particle, float deltaT) { if (_initialDelay > 0f) { return; } effectParticlePeriodic (particle, deltaT); }
/// <summary> /// Override by the derived classes to describe how new particles are emitted /// </summary> /// <param name="particleCount">Particle count.</param> /// <param name="receiver">Receiver.</param> protected virtual void emitParticles(int particleCount, ReceiverHandler receiver) { SSParticle newParticle = new SSParticle(); for (int i = 0; i < particleCount; ++i) { configureNewParticle(newParticle); receiver(newParticle); } }
protected override void emitParticles(int particleCount, ReceiverHandler particleReceiver) { SSParticle newParticle = new SSParticle(); ParticlesFieldGenerator.NewParticleDelegate fieldReceiver = (id, pos) => { configureNewParticle(newParticle); newParticle.pos = pos; particleReceiver(newParticle); return(true); }; m_fieldGenerator.Generate(particleCount, fieldReceiver); }
/// <summary> /// Essentially wrapper for effectParticle() with some masking logic for advanced particle-effector /// relantionships /// /// Override with caution. /// </summary> public void simulateParticleEffect (SSParticle particle, float deltaT) { bool match; if (maskMathFunction == MatchFunction.And) { match = ((particle.effectorMask & this.effectorMask) != 0); } else { // Equals match = (particle.effectorMask == this.effectorMask); } if (match) { effectParticle (particle, deltaT); } }
protected override void emitParticles(int particleCount, ReceiverHandler particleReceiver) { SSParticle newParticle = new SSParticle(); BodiesFieldGenerator.NewBodyDelegate bodyReceiver = (id, scale, pos, orient) => { configureNewParticle(newParticle); newParticle.pos = pos; newParticle.masterScale *= scale; newParticle.orientation += OpenTKHelper.QuaternionToEuler(ref orient); particleReceiver(newParticle); return(true); }; _bodiesGenerator.Generate(particleCount, bodyReceiver); }
protected override void effectParticlePeriodic(SSParticle particle, float timeDelta) { for (int i = 0; i < _blasts.Count; ++i) { BlastInfo bi = _blasts [i]; // TODO inverse square law or something similar Vector3 dist = particle.pos - bi.center; if (dist != Vector3.Zero) { float acc = _adsr.computeLevel (bi.timeElapsed) * bi.forceMagnitude / dist.LengthSquared / particle.mass; Vector3 dir = (particle.pos - bi.center).Normalized (); particle.vel += (acc * dir); } } }
protected void particleSwap(int leftIdx, int rightIdx) { // TODO Consider swaping on a per component basis. // It may have better peformance // But adds more per-component maintenance SSParticle leftParticle = new SSParticle(); SSParticle rightParticle = new SSParticle(); readParticle(leftIdx, leftParticle); readParticle(rightIdx, rightParticle); // write in reverse writeParticle(leftIdx, rightParticle); writeParticle(rightIdx, leftParticle); }
protected override void effectParticle(SSParticle particle, float deltaT) { Vector3 dir = particle.vel; // orient to look right float x = dir.X; float y = dir.Y; float z = dir.Z; float xy = dir.Xy.Length; float phi = (float)Math.Atan(z / xy); float theta = (float)Math.Atan2(y, x); particle.orientation.Y = -phi; particle.orientation.Z = theta; particle.orientation.X = -_orientationX; }
protected override void readParticle(int idx, SSParticle p) { base.readParticle(idx, p); var ts = (STrailsSegment)p; ts.cylInnerColor = Color4Helper.FromUInt32(_readElement(_cylInnerColors, idx).Color); ts.innerColorRatio = _readElement(_innerColorRatios, idx).Value; ts.outerColorRatio = _readElement(_outerColorRatios, idx).Value; ts.cylAxis = _readElement(_cylAxes, idx).Value; ts.prevJointAxis = _readElement(_prevJointAxes, idx).Value; ts.nextJointAxis = _readElement(_nextJointAxes, idx).Value; ts.cylWidth = _readElement(_cylWidths, idx).Value; ts.cylLendth = _readElement(_cylLengths, idx).Value; ts.nextSegmentIdx = _readElement(_nextSegmentData, idx); ts.prevSegmentIdx = _readElement(_prevSegmentData, idx); }
protected virtual int storeNewParticle(SSParticle newParticle) { // Apply effects before storing the new particle // This can help avoid unnecessary array expansion for new particle's values foreach (SSParticleEffector effector in _effectors) { effector.simulateParticleEffect(newParticle, simulationStep); } int writeIdx; if (_numParticles == _capacity) { destroyParticle(_nextIdxToOverwrite); // will decrement particle count writeIdx = _nextIdxToOverwrite; _nextIdxToOverwrite = nextIdx(_nextIdxToOverwrite); } else { while (isAlive(_nextIdxToWrite)) { _nextIdxToWrite = nextIdx(_nextIdxToWrite); } writeIdx = _nextIdxToWrite; if (writeIdx + 1 >= _activeBlockLength) { _activeBlockLength = writeIdx + 1; } _nextIdxToWrite = nextIdx(_nextIdxToWrite); } writeParticle(writeIdx, newParticle); _numParticles++; float distFromOrogin = newParticle.pos.Length; if (distFromOrogin > _radius) { _radius = distFromOrogin; } return(writeIdx); }
public virtual void removeEffector(SSParticleEffector effector) { if (effector.preRemoveHook != null) { for (int i = 0; i < _activeBlockLength; ++i) { if (isAlive(i)) { ushort effectorMask = (ushort)((int)_readElement(_effectorMasksHigh, i) << 8 | (int)_readElement(_effectorMasksLow, i)); if (effector.effectorMaskCheck(effectorMask)) { SSParticle particle = new SSParticle(); readParticle(i, particle); effector.preRemoveHook(particle); writeParticle(i, particle); } } } } _effectors.Remove(effector); }
protected override void configureNewParticle(SSParticle p) { base.configureNewParticle(p); float r = Interpolate.Lerp(radiusOffsetMin, radiusOffsetMax, nextFloat()); float theta = Interpolate.Lerp(thetaMin, thetaMax, nextFloat()); float phi = Interpolate.Lerp(phiMin, phiMax, nextFloat()); float xy = (float)Math.Cos(phi); float x = xy * (float)Math.Cos(theta); float y = xy * (float)Math.Sin(theta); float z = (float)Math.Sin(phi); Vector3 xyz = new Vector3(x, y, z); p.pos = center + r * xyz; float velocityMag = Interpolate.Lerp(velocityMagnitudeMin, velocityMagnitudeMax, nextFloat()); p.vel = velocityMag * xyz; if (orientAwayFromCenter) { p.orientation.Z = -theta; p.orientation.Y = phi; } }
public virtual void addEffector(SSParticleEffector effector) { effector.reset(); if (effector.preAddHook != null) { for (int i = 0; i < _activeBlockLength; ++i) { if (isAlive(i)) { ushort effectorMask = (ushort)((int)readData(_effectorMasksHigh, i) << 8 | (int)readData(_effectorMasksLow, i)); if (effector.effectorMaskCheck(effectorMask)) { SSParticle particle = new SSParticle(); readParticle(i, particle); effector.preAddHook(particle); writeParticle(i, particle); } } } } _effectors.Add(effector); }
protected override void configureNewParticle(SSParticle p) { base.configureNewParticle(p); p.pos = position; }
/// <summary> /// To be used by derived classes for shared particle setup /// </summary> /// <param name="p">particle to setup</param> protected virtual void configureNewParticle(SSParticle p) { p.life = Interpolate.Lerp(lifeMin, lifeMax, nextFloat()); p.componentScale.X = Interpolate.Lerp(componentScaleMin.X, componentScaleMax.X, nextFloat()); p.componentScale.Y = Interpolate.Lerp(componentScaleMin.Y, componentScaleMax.Y, nextFloat()); p.componentScale.Z = Interpolate.Lerp(componentScaleMin.Z, componentScaleMax.Z, nextFloat()); if (billboardXY) { p.billboardXY = true; } else { p.orientation.X = Interpolate.Lerp(_orientationMin.X, _orientationMax.X, nextFloat()); p.orientation.Y = Interpolate.Lerp(_orientationMin.Y, _orientationMax.Y, nextFloat()); } p.orientation.Z = Interpolate.Lerp(_orientationMin.Z, _orientationMax.Z, nextFloat()); p.angularVelocity.X = Interpolate.Lerp(angularVelocityMin.X, angularVelocityMax.X, nextFloat()); p.angularVelocity.Y = Interpolate.Lerp(angularVelocityMin.Y, angularVelocityMax.Y, nextFloat()); p.angularVelocity.Z = Interpolate.Lerp(angularVelocityMin.Z, angularVelocityMax.Z, nextFloat()); p.vel.X = Interpolate.Lerp(velocityComponentMin.X, velocityComponentMax.X, nextFloat()); p.vel.Y = Interpolate.Lerp(velocityComponentMin.Y, velocityComponentMax.Y, nextFloat()); p.vel.Z = Interpolate.Lerp(velocityComponentMin.Z, velocityComponentMax.Z, nextFloat()); p.masterScale = Interpolate.Lerp(masterScaleMin, masterScaleMax, nextFloat()); p.mass = Interpolate.Lerp(massMin, massMax, nextFloat()); p.rotationalInnertia = Interpolate.Lerp(rotationalInnertiaMin, rotationalInnertiaMax, nextFloat()); p.drag = Interpolate.Lerp(dragMin, dragMax, nextFloat()); p.rotationalDrag = Interpolate.Lerp(rotationalDragMin, rotationalDragMax, nextFloat()); // color presets Color4 randPreset; if (colorPresets != null && colorPresets.Length > 0) { randPreset = colorPresets [_rand.Next(0, colorPresets.Length)]; } else { randPreset = new Color4(0f, 0f, 0f, 0f); } // color offsets Color4 randOffset; randOffset.R = Interpolate.Lerp(colorOffsetComponentMin.R, colorOffsetComponentMax.R, nextFloat()); randOffset.G = Interpolate.Lerp(colorOffsetComponentMin.G, colorOffsetComponentMax.G, nextFloat()); randOffset.B = Interpolate.Lerp(colorOffsetComponentMin.B, colorOffsetComponentMax.B, nextFloat()); randOffset.A = Interpolate.Lerp(colorOffsetComponentMin.A, colorOffsetComponentMax.A, nextFloat()); // color presets + offsets p.color = Color4Helper.Add(ref randPreset, ref randOffset); //p.SpriteIndex = SpriteIndices [s_rand.Next(0, SpriteIndices.Length)]; p.spriteRect = spriteRectangles [_rand.Next(0, spriteRectangles.Length)]; p.effectorMask = effectorMasks [_rand.Next(0, effectorMasks.Length)]; }
protected virtual void simulateStep() { _radius = 0f; foreach (SSParticleEffector effector in _effectors) { effector.simulateSelf(simulationStep); } foreach (SSParticleEmitter emitter in _emitters) { emitter.simulateSelf(simulationStep); } SSParticle p = new SSParticle(); for (int i = 0; i < _activeBlockLength; ++i) { if (isAlive(i)) { // Alive particle _lives [i] -= simulationStep; if (isAlive(i)) { // Still alive. Update position and run through effectors readParticle(i, p); p.vel -= p.drag * p.vel / p.mass; if (!p.billboardXY) { p.angularVelocity.Xy -= p.rotationalDrag * p.angularVelocity.Xy / p.rotationalInnertia; } p.angularVelocity.Z -= p.rotationalDrag * p.angularVelocity.Z / p.rotationalInnertia; p.pos += p.vel * simulationStep; if (!p.billboardXY) { p.orientation.Xy += p.angularVelocity.Xy * simulationStep; } p.orientation.Z += p.angularVelocity.Z * simulationStep; foreach (SSParticleEffector effector in _effectors) { effector.simulateParticleEffect(p, simulationStep); } writeParticle(i, p); float distFromOrogin = p.pos.Length; if (distFromOrogin > _radius) { _radius = distFromOrogin; } #if false Console.WriteLine(p.life.ToString() + '\t' + p.pos.X + '\t' + p.pos.Y + '\t' + p.pos.Z + '\t' + p.vel.X + '\t' + p.vel.Y + '\t' + p.vel.Z + '\t' ); #endif } else { // Particle just died. Hack to not draw? writeDataIfNeeded(ref _positions, i, _notAPosition); if (_numParticles == _capacity || i < _nextIdxToWrite) { // released slot will be the next one to be written to _nextIdxToWrite = i; } if (i == _activeBlockLength - 1) { // reduction in the active particles block while (_activeBlockLength > 0 && isDead(_activeBlockLength - 1)) { --_activeBlockLength; } } --_numParticles; if (_numParticles == 0) { // all particles gone. reset write and overwrite locations for better packing _nextIdxToWrite = 0; _nextIdxToOverwrite = 0; _activeBlockLength = 0; } } } } foreach (SSParticleEmitter emitter in _emitters) { emitter.simulateEmissions(simulationStep, storeNewParticle); } }
protected override void applyValue(SSParticle particle, float value) { particle.masterScale = amplification * value; }
protected virtual void simulateStep() { _radius = 0f; Vector3 centerAccumulator = Vector3.Zero; foreach (SSParticleEffector effector in _effectors) { effector.simulateSelf(simulationStep); } foreach (SSParticleEmitter emitter in _emitters) { emitter.simulateSelf(simulationStep); } SSParticle p = createNewParticle(); for (int i = 0; i < _activeBlockLength; ++i) { if (isAlive(i)) { // Alive particle _lives [i] -= simulationStep; if (isAlive(i)) { // Still alive. Update position and run through effectors readParticle(i, p); p.vel -= p.drag * p.vel / p.mass; if (!p.billboardXY) { p.angularVelocity.Xy -= p.rotationalDrag * p.angularVelocity.Xy / p.rotationalInnertia; } p.angularVelocity.Z -= p.rotationalDrag * p.angularVelocity.Z / p.rotationalInnertia; p.pos += p.vel * simulationStep; if (!p.billboardXY) { p.orientation.Xy += p.angularVelocity.Xy * simulationStep; } p.orientation.Z += p.angularVelocity.Z * simulationStep; foreach (SSParticleEffector effector in _effectors) { effector.simulateParticleEffect(p, simulationStep); } writeParticle(i, p); centerAccumulator += p.pos; float distFromOrogin = (p.pos - _center).Length; if (distFromOrogin > _radius) { _radius = distFromOrogin; } #if false Console.WriteLine(p.life.ToString() + '\t' + p.pos.X + '\t' + p.pos.Y + '\t' + p.pos.Z + '\t' + p.vel.X + '\t' + p.vel.Y + '\t' + p.vel.Z + '\t' ); #endif } else { destroyParticle(i); } } } _center = centerAccumulator / _numParticles; foreach (SSParticleEmitter emitter in _emitters) { emitter.simulateEmissions(simulationStep, createNewParticle, storeNewParticle); } }
/// <summary> /// Where the logic for changing particle actually takes place /// /// For example, particle.Vel += new Vector3(1f, 0f, 0f) * dT simulates /// acceleration on the X axis. Multiple effectors will combine their /// acceleration effect to determine the final velocity of the particle. // /// </summary> protected abstract void effectParticle(SSParticle particle, float deltaT);
protected abstract void applyValue(SSParticle particle, T value);
protected virtual void effectParticlePeriodic(SSParticle particle, float deltaT) { }
protected override void applyValue(SSParticle particle, Vector3 value) { particle.componentScale = baseOffset + amplification * value; }