/*
* SplineEmitter.cs
* Authors: Brian Murphy
* Copyright (c) 2007-2008 Cornell University
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Random=Util.Random;
namespace TACParticleEngine.Emitters
{
///
/// This class is an emitter that allows for any kind of shape via a single continous line.
/// Curves are formed via catmull-rom splines based off of a series of points.
///
public class SplineEmitter : Emitter
{
#region Fields
// Stored positions for renewing spline effects after scaling
private Vector3[] mSavedPositions;
// Vector used for storing the return values for position and velocity of a particle
private Vector3[] mReturnVal;
// Vectors used within the catmull-rom spline equation for calculating position and velocity
private Vector3 mBeginPoint, mEndPoint;
// Used for randomizing the values obtained from the catmull-rom spline equation
private double mRandVal;
// Temporary int used for storing how many particles were created every cycle
private int mTempAmount;
// Temporary integers for use in iterating over the particle heap
private int mTempIndex, mTempIndex2;
// Current index of the next available spot in the head for adding a particle to
private int mCurrentIndex;
// Float used for determining particle creation
private float mTotalElapsedTime;
// Float used for determining scaling
private float mScaleElapsedTime;
#endregion
#region Form Fields
// Whether or not the old emitter positions should be overwritten. Used for particle editor
private bool mOverrideOldPositions;
// Vector3 offset for all emmitter positions so only one value needs to be set to move all points
// in order to retain the original particle shape
private Vector3 mEmitterPositionOffset;
// Current positions of the various emitter points
private Vector3[] mEmitterPositions;
// 3D Vector used for fixed directions, only used if set
private Vector3 mDirection;
// Scale size of positions in relation to origin
private float mScale;
// The speed at which the scaling occurs
private float mScaleSpeed = 0.1f;
#endregion
#region Properties
///
/// Gets/Sets whether this emitter is active or not
/// *Note* setting active resets it to its original non-scaled position
///
public override Boolean IsActive
{
get { return mIsActive; }
set
{
mIsActive = value;
if (mIsActive)
{
mParticlesChanged = true;
mCurrentEmitLife = 0;
mEmitterPositions = (Vector3[])mSavedPositions.Clone();
}
else
{
mCurrentEmitLife = mEmitLifetime;
}
}
}
#endregion
#region Form Properties
///
/// Gets/Sets the current position offset of the entire spline base
///
public override Vector3 EmitterPosition
{
get { return mEmitterPositionOffset; }
set { mEmitterPositionOffset = value; }
}
///
/// Gets/Sets the current positions of the spline base
///
public Vector3[] EmitterPositions {
get { return mEmitterPositions; }
set
{
mEmitterPositions = value;
if (mOverrideOldPositions)
{
mSavedPositions = mEmitterPositions;
}
}
}
///
/// Gets/Sets the direction of the emitter
///
public override Vector3 Direction {
get { return mDirection; }
set { mDirection = value; }
}
///
/// Gets/Sets the scaling value for positions for every update
///
public float Scale
{
get { return mScale; }
set { mScale = value; }
}
///
/// Gets/Sets the speed at which the scaling occurs
///
public float ScaleSpeed
{
get { return mScaleSpeed; }
set { mScaleSpeed = value; }
}
#endregion
#region Creation
///
/// Default constructor for use in the Particle Editor
///
public SplineEmitter()
: base(SpriteBlendMode.AlphaBlend, "", 100, 1000, -1, 1, Vector2.One, Vector2.One, Vector2.One,
new Vector2(500, 500), 0, 0, 0, 0, Vector4.One, Vector4.One, Vector4.Zero, Vector4.Zero)
{
mOverrideOldPositions = true;
mCurrentIndex = 0;
mSavedPositions = new Vector3[1];
mSavedPositions[0] = Vector3.Zero;
mEmitterPositions = (Vector3[])mSavedPositions.Clone();
mTotalElapsedTime = 0;
mScale = 1f;
mScaleSpeed = 0.1f;
mReturnVal = new Vector3[2];
}
///
/// Constructor
///
/// Initial positions to start at
/// Amount that the emitter scales in size every scale iteration. 1 means no change
/// How often the scaling occurs in seconds
/// SpriteBlendMode to use
/// Name of the texture to draw with
/// How many particles per second are made
/// The max amount of particles available at one time
/// How long the emitter stays active for
/// How long the patrticle stays active for
/// How small a particle can start
/// How large a particle can start
/// How small a particle can end
/// How large a particle can end
/// How slow a particle can start
/// How fast a particle can start
/// How slow a particle can end
/// How fast a particle can end
/// Minimum values for color to start with
/// Maximum values for color to start with
/// Minimum values for color to end with
/// Maximum values for color to end with
public SplineEmitter(Vector3[] positions, float scale, float scaleSpeed, SpriteBlendMode blendEffect,
string textureName, float particlesPerSec, int particleAmount, float emitLifetime, float particleLifetime,
Vector2 minStartSize, Vector2 maxStartSize, Vector2 minEndSize, Vector2 maxEndSize, float minStartSpeed,
float maxStartSpeed, float minEndSpeed, float maxEndSpeed, Vector4 minStartColor, Vector4 maxStartColor,
Vector4 minEndColor, Vector4 maxEndColor)
: base(blendEffect, textureName, particlesPerSec, particleAmount, emitLifetime,
particleLifetime, minStartSize, maxStartSize, minEndSize, maxEndSize, minStartSpeed, maxStartSpeed,
minEndSpeed, maxEndSpeed, minStartColor, maxStartColor, minEndColor, maxEndColor)
{
mOverrideOldPositions = true;
mCurrentIndex = 0;
mSavedPositions = positions;
mEmitterPositions = (Vector3[])mSavedPositions.Clone();
mTotalElapsedTime = 0;
mScale = scale;
mScaleSpeed = ScaleSpeed;
mReturnVal = new Vector3[2];
}
///
/// Returns a copy of the spline emitter
///
/// A cloned copy of this spline emitter
public override object Clone()
{
SplineEmitter retVal = new SplineEmitter((Vector3[])mSavedPositions.Clone(), mScale, mScaleSpeed, base.mBlendEffect, base.mTextureName,
base.mParticlesPerSec, base.mParticleAmount, base.mEmitLifetime, base.mParticleLifetime, base.mMinStartSize,
base.mMaxStartSize, base.mMinEndSize, base.mMaxEndSize, base.mMinStartSpeed, base.mMaxStartSpeed,
base.mMinEndSpeed, base.mMaxEndSpeed, base.mMinStartColor, base.mMaxStartColor, base.mMinEndColor,
base.mMaxEndColor);
retVal.mTexture = mTexture;
return retVal;
}
#endregion
#region Update
///
/// Updates spline emitter
///
/// The time elapsed since last tick in milliseconds
public override void Update(float elapsedTime)
{
if (base.mParticles != null)
{
base.Update(elapsedTime);
if (base.Alive && base.IsActive)
{
mScaleElapsedTime += elapsedTime;
if (mScaleElapsedTime > mScaleSpeed)
{
mOverrideOldPositions = false;
for (int i = mEmitterPositions.Length - 1; i >= 0; --i)
{
mEmitterPositions[i] = Vector3.Multiply(mEmitterPositions[i], mScale);
}
mScaleElapsedTime -= mScaleSpeed;
mOverrideOldPositions = true;
}
mTotalElapsedTime += elapsedTime;
mTempAmount = (int)(mTotalElapsedTime * base.mParticlesPerSec);
if (mTempAmount > 0)
{
mTempIndex = (mCurrentIndex + mTempAmount) % base.mParticles.Length;
if (mCurrentIndex + mTempAmount < base.mParticles.Length)
{
for (; mCurrentIndex < mTempIndex; mCurrentIndex++)
{
CalcPosition();
base.InitializeParticle(mReturnVal[0], mReturnVal[1], mCurrentIndex);
}
}
else
{
for (; mCurrentIndex < base.mParticles.Length; mCurrentIndex++)
{
CalcPosition();
base.InitializeParticle(mReturnVal[0], mReturnVal[1], mCurrentIndex);
}
mCurrentIndex = 0;
for (; mCurrentIndex < mTempIndex; mCurrentIndex++)
{
CalcPosition();
base.InitializeParticle(mReturnVal[0], mReturnVal[1], mCurrentIndex);
}
}
mTotalElapsedTime = 0;
}
}
}
}
#endregion
#region Util
///
/// Randomly calculates what spot along the spline and in which direction (normal or anti-normal)
/// for a particle to start at and with
///
private void CalcPosition()
{
if (mEmitterPositions.Length == 1)
{
mReturnVal[0] = mEmitterPositions[0];
double tempXRot = Random.NextFloat(MathHelper.TwoPi);
double tempYRot = Random.NextFloat(MathHelper.TwoPi);
mReturnVal[1].X = (float)Math.Cos(tempYRot);
mReturnVal[1].Y = (float)Math.Sin(tempXRot);
mReturnVal[1].Z = (float)(Math.Sin(tempYRot) + Math.Cos(tempXRot));
mReturnVal[1].Normalize();
}
else
{
mTempIndex2 = Random.Next(mEmitterPositions.Length - 1);
mRandVal = Random.NextDouble();
if (mTempIndex2 == 0)
{
mBeginPoint = 2 * mEmitterPositions[mTempIndex2] - mEmitterPositions[mTempIndex2 + 1];
}
else
{
mBeginPoint = mEmitterPositions[mTempIndex2 - 1];
}
if (mTempIndex2 + 2 >= mEmitterPositions.Length)
{
mEndPoint = 2 * mEmitterPositions[mTempIndex2 + 1] - mEmitterPositions[mTempIndex2];
}
else
{
mEndPoint = mEmitterPositions[mTempIndex2 + 2];
}
mReturnVal[0] = 0.5f * ((2 * mEmitterPositions[mTempIndex2]) + (mEmitterPositions[mTempIndex2 + 1] -
mBeginPoint) * (float)mRandVal + (2 * mBeginPoint - 5 * mEmitterPositions[mTempIndex2] + 4 *
mEmitterPositions[mTempIndex2 + 1] - mEndPoint) * (float)Math.Pow(mRandVal, 2.0) + (3 *
mEmitterPositions[mTempIndex2] - mBeginPoint - 3 * mEmitterPositions[mTempIndex2 + 1] +
mEndPoint) * (float)Math.Pow(mRandVal, 3.0));
mReturnVal[0] += mEmitterPositionOffset;
if (mRandomDir)
{
double tempXRot = Random.NextFloat(MathHelper.TwoPi);
double tempYRot = Random.NextFloat(MathHelper.TwoPi);
mReturnVal[1].X = (float)Math.Cos(tempYRot);
mReturnVal[1].Y = (float)Math.Sin(tempXRot);
mReturnVal[1].Z = (float)(Math.Sqrt(Math.Pow(Math.Sin(tempYRot), 2.0) + Math.Pow(Math.Cos(tempXRot), 2.0)));
}
else if (!mDirection.Equals(Vector3.Zero))
{
mReturnVal[1] = mDirection;
}
else
{
mReturnVal[1] = 0.5f * ((mEmitterPositions[mTempIndex2 + 1] - mBeginPoint) + 2 * (2 * mBeginPoint -
5 * mEmitterPositions[mTempIndex2] + 4 * mEmitterPositions[mTempIndex2 + 1] - mEndPoint) *
(float)mRandVal + 3 * (3 * mEmitterPositions[mTempIndex2] - mBeginPoint - 3 *
mEmitterPositions[mTempIndex2 + 1] + mEndPoint) * (float)Math.Pow(mRandVal, 2.0));
mReturnVal[1].Normalize();
mReturnVal[1] = new Vector3((mReturnVal[1].Y * Random.NextFloat() + mReturnVal[1].Z * Random.NextFloat()),
(mReturnVal[1].X * Random.NextFloat() + mReturnVal[1].Z * Random.NextFloat()) * -1,
(mReturnVal[1].X * Random.NextFloat() + mReturnVal[1].Y * Random.NextFloat()));
}
}
}
#endregion
}
}