/*
 * Javascript/Canvas Textured 3D Renderer v0.3
 * Particle Generator
 * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com
 * This software is free to use for non-commercial purposes. For anything else, please contact the author.
 */

(function() {

Canvas3D.ParticleGenerator = function() {
	this._oPosition = new Canvas3D.Vec3(0,0,0);

	this._bStarted = false;

	this._bDrawEmitter = false; // draw a yellow dot at emission point

	this.reset();

	this._bDirty = true;
};

var proto = Canvas3D.ParticleGenerator.prototype;

// initialize and reset particle settings to a small stream of magenta particles going up
proto.reset = function() 
{
	this._oDirection = new Canvas3D.Vec3(0,1,0);
	this._oDirectionNoise = new Canvas3D.Vec3(0,0,0);
	this._oParticleRotation = new Canvas3D.Vec3(0,0,0);
	this._oEmissionOffset = new Canvas3D.Vec3(0,0,0);
	this._iTimerInterval = 50;
	this._iLastUpdate = new Date().getTime();
	this._aParticles = [];
	this._iParticleCount = 100;
	this._oParticleColor = {r:255,g:0,b:255,a:1};
	this._oParticleColorNoise = {r:0,g:0,b:0,a:0};
	this._fParticleSize = 5;
	this._fParticleSizeNoise = 0;
	this._strParticleShape = "filled_circle"; // "filled_circle", "circle", "square", "filled_square"
	this._iParticleSpawnSpeed = 1000;
	this._fParticleSpeed = 1;
	this._fParticleResistance = 0.00;
	this._iParticleLife = 5000;
	this._iParticleLifeNoise = 0;
	this._fncParticleCallback = null;
	this._aForcePoints = [];
	this._aForceDirections = [];
	this._bSpawnAll = false;
}



proto.addForceDirection = function(oDir, fStrength)
{
	this._aForceDirections.push(
		{
			dir : oDir,
			strength : fStrength
		}
	)
	this._bDirty = true;
}

proto.addForcePoint = function(oPos, fStrength)
{
	this._aForcePoints.push(
		{
			pos : oPos,
			strength : fStrength
		}
	)
	this._bDirty = true;
}

proto.setParticleColor = function(oColor) 
{
	this._oParticleColor = oColor;
	this._bDirty = true;
}

proto.getParticleColor = function() 
{
	return this._oParticleColor;
}

proto.setParticleColorNoise = function(oColor) 
{
	this._oParticleColorNoise = oColor;
	this._bDirty = true;
}
proto.getParticleColorNoise = function() 
{
	return this._oParticleColorNoise;
}

proto.setParticleResistance = function(fRes) 
{
	this._fParticleResistance = fRes;
	this._bDirty = true;
}
proto.getParticleResistance = function() 
{
	return this._fParticleResistance;
}



proto.start = function() 
{
	this._bRunning = true;

	// if first time, set up timer
	if (!this._bStarted) {
		this._iLastUpdate = new Date().getTime();
		this._checkSpawn();
		this._run();
	}
}

proto.stop = function() 
{
	this._bRunning = false;
}

// todo: change so timer is not unnecessarily running when paused
proto._run = function() 
{
	if (this._bRunning) {
		this._nextFrame();
	}
	var me = this;
	setTimeout(
		function() {
			me._run();
		}, this._iTimerInterval
	);
}

proto._checkSpawn = function() 
{
	if (this._bRunning) {
		var iTime = new Date().getTime();

		var aParticles = this._aParticles;
		var aLiveParticles = [];
		for (var i=0;i<aParticles.length;i++) {
			var oParticle = aParticles[i];
			if (oParticle.life == -1 || iTime - oParticle.birth < oParticle.life) {
				aLiveParticles.push(oParticle);
			}
		}
		this._aParticles = aParticles = aLiveParticles;

		if (aParticles.length < this._iParticleCount) {
			if (this._bSpawnAll) {
				while (aParticles.length < this._iParticleCount) {
					this._spawnParticle();
				}
			} else {
				this._spawnParticle();
			}
		}

	}
	var me = this;
	setTimeout(
		function() {
			me._checkSpawn();
		}, this._iParticleSpawnSpeed
	);
}


proto._nextFrame = function()
{
	var iTime = new Date().getTime();
	var iDelta = iTime - this._iLastUpdate;

	var fTimeDelta = iDelta / 1000;

	var aParticles = this._aParticles;
	for (var i=0;i<aParticles.length;i++) {
		var oParticle = aParticles[i];

		if (this._fncParticleCallback) {
			var iCurLife = iTime - oParticle.birth;
			this._fncParticleCallback(oParticle, iCurLife / oParticle.life);
		}

		var oDir = oParticle.dir;
		var oPos = oParticle.pos;

		var oDeltaDir = new Canvas3D.Vec3(0,0,0);

		for (var g=0;g<this._aForceDirections.length;g++) {
			var oForce = this._aForceDirections[g];
			oDeltaDir.x += oForce.dir.x * oForce.strength * fTimeDelta;
			oDeltaDir.y += oForce.dir.y * oForce.strength * fTimeDelta;
			oDeltaDir.z += oForce.dir.z * oForce.strength * fTimeDelta;
		}

		// this is unstable and gives bad results when timedelta * force makes the particle move through the attraction point
		for (var g=0;g<this._aForcePoints.length;g++) {
			var oForce = this._aForcePoints[g];
			if (oForce.pos.x != oPos.x || oForce.pos.y != oPos.y || oForce.pos.z != oPos.z) {

				var fDeltaX = oForce.pos.x - oPos.x;
				var fDeltaY = oForce.pos.y - oPos.y;
				var fDeltaZ = oForce.pos.z - oPos.z;

				var fStrength = oForce.strength;

				var oForceDir = new Canvas3D.Vec3(fDeltaX,fDeltaY,fDeltaZ).unit();

				oDeltaDir.x += oForceDir.x * fStrength * fTimeDelta;
				oDeltaDir.y += oForceDir.y * fStrength * fTimeDelta;
				oDeltaDir.z += oForceDir.z * fStrength * fTimeDelta;
			}
		}

		oDir.x += oDeltaDir.x;
		oDir.y += oDeltaDir.y;
		oDir.z += oDeltaDir.z;

		oDir.x *= (1 - this._fParticleResistance * fTimeDelta);
		oDir.y *= (1 - this._fParticleResistance * fTimeDelta);
		oDir.z *= (1 - this._fParticleResistance * fTimeDelta);

		oParticle.pos.x += oDir.x * fTimeDelta;
		oParticle.pos.y += oDir.y * fTimeDelta;
		oParticle.pos.z += oDir.z * fTimeDelta;

		var oRot = oParticle.rot;
		if (oRot.x != 0)
			oParticle.pos.rotateX(oRot.x * Math.PI / 180);
		if (oRot.y != 0)
			oParticle.pos.rotateY(oRot.y * Math.PI / 180);
		if (oRot.z != 0)
			oParticle.pos.rotateZ(oRot.z * Math.PI / 180);

	}

	this._bDirty = true;
	this._iLastUpdate = iTime;

}

proto._spawnParticle = function()
{
	var oPos = this._oPosition;
	var oOffset = this._oEmissionOffset
	var oDir = this._oDirection;

	var oParticleDir = new Canvas3D.Vec3(oDir.x, oDir.y, oDir.z);

	var fNoiseDirX = Math.random() - 0.5;
	var fNoiseDirY = Math.random() - 0.5;
	var fNoiseDirZ = Math.random() - 0.5;

	oParticleDir.x += this._oDirectionNoise.x * fNoiseDirX;
	oParticleDir.y += this._oDirectionNoise.y * fNoiseDirY;
	oParticleDir.z += this._oDirectionNoise.z * fNoiseDirZ;

	oParticleDir = oParticleDir.unit();

	oParticleDir.x *= this._fParticleSpeed;
	oParticleDir.y *= this._fParticleSpeed;
	oParticleDir.z *= this._fParticleSpeed;

	var oColor = {
		r: Math.min(255, Math.max(0, this._oParticleColor.r + Math.round((Math.random()-0.5) * this._oParticleColorNoise.r))),
		g: Math.min(255, Math.max(0, this._oParticleColor.g + Math.round((Math.random()-0.5) * this._oParticleColorNoise.g))),
		b: Math.min(255, Math.max(0, this._oParticleColor.b + Math.round((Math.random()-0.5) * this._oParticleColorNoise.b))),
		a: Math.min(1, Math.max(0, this._oParticleColor.a + ((Math.random()-0.5) * this._oParticleColorNoise.a)))
	};

	var oRot = new Canvas3D.Vec3(this._oParticleRotation.x, this._oParticleRotation.y, this._oParticleRotation.z);

	var oParticle = {
		pos : new Canvas3D.Vec3(oPos.x + oOffset.x, oPos.y + oOffset.y, oPos.z + oOffset.z),
		color : oColor,
		dir : oParticleDir,
		birth : new Date().getTime(),
		life : this._iParticleLife == -1 ? -1 : this._iParticleLife + (Math.random() * (this._iParticleLifeNoise/2 - this._iParticleLifeNoise)),
		size : this._fParticleSize + (Math.random() * (this._fParticleSizeNoise/2 - this._fParticleSizeNoise)),
		rot : oRot
		
	}

	if (this._fncParticleCallback) {
		this._fncParticleCallback(oParticle, 0);
	}
	this._aParticles.push(oParticle);
}



proto.getIntensity = function(fIntensity) 
{
	return this._fIntensity;
}

proto.setScene = function(oScene)
{
	if (this._oScene != oScene) {
		this._oScene = oScene;
	}
}

proto.draw = function(oContext, iOffsetX, iOffsetY) 
{
	var oScene = this._oScene;
	var oCam = oScene.getActiveCamera();

	var iFocal = oCam.getFocal();

	if (this._bDrawEmitter) {
		var oPos2D = oCam.transform2D(oCam.transformPoint(this._oPosition));
		var iRadius = 3;
		oContext.beginPath();
		oContext.moveTo(oPos2D.x + iOffsetX + iRadius, oPos2D.y + iOffsetY);
		oContext.arc(oPos2D.x + iOffsetX, oPos2D.y + iOffsetY, iRadius, 0, 360, false);
		oContext.fillStyle = "rgb(255,255,0)";
		oContext.fill();
	}

	var aParticles = this._aParticles;
	for (var i=0;i<aParticles.length;i++) {
		var oParticle = aParticles[i];
		var oColor = oParticle.color;

		var oTransPos = oCam.transformPoint(oParticle.pos);
		var oPos2D = oCam.transform2D(oTransPos);

		var fDist = Math.sqrt(oTransPos.x*oTransPos.x + oTransPos.y*oTransPos.y + oTransPos.z*oTransPos.z);

		var fScaleRatio = iFocal / (iFocal + fDist);

		var iParticleSize = Math.max(1, Math.floor(oParticle.size * fScaleRatio));

		var iParticleOffset = Math.floor(iParticleSize / 2);

		var strColor = "rgba(" + oColor.r + "," + oColor.g + "," + oColor.b + "," + oColor.a + ")";

		if (this._strParticleShape == "circle" || this._strParticleShape == "filled_circle") {
			oContext.beginPath();
			oContext.moveTo(oPos2D.x + iOffsetX + iParticleSize, oPos2D.y + iOffsetY);
			oContext.arc(oPos2D.x + iOffsetX, oPos2D.y + iOffsetY, iParticleSize, 0, 360, false);
			if (this._strParticleShape == "circle") {
				oContext.strokeStyle = strColor;
				oContext.stroke();
			} else {
				oContext.fillStyle = strColor;
				oContext.fill();
			}
		} else if (this._strParticleShape == "square" || this._strParticleShape == "filled_square") {
			oContext.beginPath();
			
			if (this._strParticleShape == "square") {
				oContext.strokeStyle = strColor;
				oContext.strokeRect(oPos2D.x + iOffsetX - iParticleOffset, oPos2D.y + iOffsetY - iParticleOffset, iParticleSize, iParticleSize);
			} else {
				oContext.fillStyle = strColor;
				oContext.fillRect(oPos2D.x + iOffsetX - iParticleOffset, oPos2D.y + iOffsetY - iParticleOffset, iParticleSize, iParticleSize);
			}
		}

	}

	this._bDirty = false;
}


proto.getDirty = function()
{
	return this._bDirty;
}

proto.setForcedZ = function(iZ)
{
	this._iForcedZ = iZ;
}

proto.getForcedZ = function()
{
	return this._iForcedZ;
}

proto.getHideWhenRotating = function()
{
	return this._bHideWhenRotating;
}

proto.setHideWhenRotating = function(bEnable)
{
	this._bHideWhenRotating = bEnable;
}



proto.setParticleCallback = function(fncCallback) 
{
	this._fncParticleCallback = fncCallback;
}
proto.getParticleCallback = function() 
{
	return this._fncParticleCallback;
}

proto.setPosition = function(oPos) 
{
	this._oPosition = oPos;
}
proto.getPosition = function() 
{
	return this._oPosition;
}


proto.setEmissionOffset = function(oPos) 
{
	this._oEmissionOffset = oPos;
}
proto.getEmissionOffset = function() 
{
	return this._oEmissionOffset;
}

proto.setDirection = function(oDir) 
{
	this._oDirection = oDir;
}
proto.getDirection = function() 
{
	return this._oDirection;
}

proto.setDirectionNoise = function(oDirNoise) 
{
	this._oDirectionNoise = oDirNoise;
}
proto.getDirectionNoise = function() 
{
	return this._oDirectionNoise;
}

proto.setParticleRotation = function(oRot) 
{
	this._oParticleRotation = oRot;
}
proto.getParticleRotation = function() 
{
	return this._oParticleRotation;
}

proto.setParticleCount = function(iMax) 
{
	this._iParticleCount = iMax;
}
proto.getParticleCount = function() 
{
	return this._iParticleCount;
}

proto.setParticleSize = function(fSize) 
{
	this._fParticleSize = fSize;
}
proto.getParticleSize = function() 
{
	return this._fParticleSize;
}

proto.setParticleSizeNoise = function(fSizeNoise) 
{
	this._fParticleSizeNoise = fSizeNoise;
}
proto.getParticleSizeNoise = function() 
{
	return this._fParticleSizeNoise;
}

proto.setParticleShape = function(strShape) 
{
	if (strShape == "circle" || strShape == "filled_circle" || strShape == "square" || strShape == "filled_square") {
		this._strParticleShape = strShape;
	}
}
proto.getParticleShape = function() 
{
	return this._strParticleShape;
}

proto.setParticleSpawnSpeed = function(iTime) 
{
	if (iTime == -1) 
		this.setSpawnAll(true);
	else if (this._iParticleSpawnSpeed == -1) 
		this.setSpawnAll(false);

	this._iParticleSpawnSpeed = iTime;
}
proto.getParticleSpawnSpeed = function() 
{
	return this._iParticleSpawnSpeed;
}

proto.setParticleSpeed = function(fSpeed) 
{
	this._fParticleSpeed = fSpeed;
}

proto.getParticleSpeed = function() 
{
	return this._fParticleSpeed;
}

proto.setParticleLife = function(iLife) 
{
	this._iParticleLife = iLife;
}
proto.getParticleLife = function() 
{
	return this._iParticleLife;
}
proto.setParticleLifeNoise = function(iLifeNoise) 
{
	this._iParticleLifeNoise = iLifeNoise;
}
proto.getParticleLifeNoise = function() 
{
	return this._iParticleLifeNoise;
}

proto.setSpawnAll = function(bEnable) 
{
	this._bSpawnAll = bEnable;
}
proto.getSpawnAll = function() 
{
	return this._bSpawnAll;
}


})();