// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "std3d.h"

#include "nel/3d/ps_zone.h"
#include "nel/3d/vertex_buffer.h"
#include "nel/3d/index_buffer.h"
#include "nel/3d/material.h"
#include "nel/3d/ps_util.h"
#include "nel/3d/dru.h"
#include "nel/3d/particle_system.h"
#include "nel/misc/plane.h"

// tmp

#include "nel/3d/particle_system_model.h"

#include <cmath>
#include <limits>

namespace NL3D {


/*
 * Constructor
 */
CPSZone::CPSZone() : _BounceFactor(1.f), _CollisionBehaviour(bounce)
{
	NL_PS_FUNC(CPSZone_CPSZone)
}

void CPSZone::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	NL_PS_FUNC(CPSZone_serial)
	f.serialVersion(1);
	CPSTargetLocatedBindable::serial(f);
	f.serialEnum(_CollisionBehaviour);
	f.serial(_BounceFactor);
	if (f.isReading())
	{
		for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it)
		{
			// though this is not a force, this prevent parametric motion
			(*it)->addNonIntegrableForceRef();
		}
	}
}

/** Add a new type of located for this zone to apply on.
* We override this to queery the target to allocate the CollisionInfo attribute
*/
void CPSZone::attachTarget(CPSLocated *ptr)
{
	NL_PS_FUNC(CPSZone_attachTarget)
	CPSTargetLocatedBindable::attachTarget(ptr);
	ptr->queryCollisionInfo();
	ptr->addNonIntegrableForceRef();
}





/// inherit from CPSTargetLocatedBindable. Its called when one of the targets has been detroyed
void CPSZone::releaseTargetRsc(CPSLocated *target)
{
	NL_PS_FUNC(CPSZone_releaseTargetRsc)
	// tell the target that we were using collision infos and that we won't use them anymore
	target->releaseCollisionInfo();
	target->releaseNonIntegrableForceRef();
}



void CPSZone::step(TPSProcessPass pass)
{
	NL_PS_FUNC(CPSZone_step)
	// for zone, the PSCollision pass and the PSToolRenderPass are processed
	switch(pass)
	{
		case PSToolRender:
			show();
			break;
		default: break;
	}
}


// build a basis with K = the normal of the plane
CMatrix CPSZonePlane::buildBasis(uint32 index) const
{
	NL_PS_FUNC(CPSZonePlane_buildBasis)
	CMatrix m;
	m.setPos(_Owner->getPos()[index]);
	CPSUtil::buildSchmidtBasis(_Normal[index], m);
	return m;
}


/// this compute a new speed vector, so that the located will reach the correct position at the next speed integration
/// this create the illusion of collision
/*void CPSZone::bounce(uint32 locatedIndex, const CVector &bouncePoint, const CVector &surfNormal, float elasticity, TAnimationTime ellapsedTime)
{
	CVector &speed = _Owner->getSpeed()[locatedIndex];
	const CVector &pos   = _Owner->getPos()[locatedIndex];
	CVector &bounceVect = elasticity  * (speed - 2.0f * (speed * surfNormal) * surfNormal); // speed vector after collision
	// now check where the located will be after integration
	CVector d = bouncePoint - pos;
	TAnimationTime collideDelay = speed.norm() / d.norm();
	CVector finalPos = bouncePoint + (ellapsedTime - collideDelay) * bounceVect;
	// now, we must have pos + ellapsedTime * newSpeed = finalPos
	// newSpeed = alpha * (finalPos - pos)
	// so alpha = 1 / ellapsedTime

	speed = (1.0f / ellapsedTime) * (finalPos - pos);
}*/


void CPSZonePlane::show()
{
	NL_PS_FUNC(CPSZonePlane_show)
	const float planeSize = 2.0f;
	setupDriverModelMatrix();
	IDriver *driver = getDriver();
	uint k  = 0;
	setupDriverModelMatrix();
	CPSLocated *loc;
	uint32 index;
	CPSLocatedBindable *lb;
	_Owner->getOwner()->getCurrentEditedElement(loc, index, lb);
	for (TPSAttribVector::const_iterator it = _Owner->getPos().begin(); it != _Owner->getPos().end(); ++it, ++k)
	{
		const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k  ? CRGBA::Red : CRGBA(127, 127, 127));
		CMatrix mat = buildBasis(k);
		CPSUtil::displayBasis(getDriver(), getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager());
		setupDriverModelMatrix();
		CDRU::drawLine(*it + (planeSize  + 3) * mat.getI() + planeSize * mat.getJ(),
					   *it - (planeSize + 3) * mat.getI() + planeSize * mat.getJ(),
					   col,
					   *driver);

		CDRU::drawLine(*it + (planeSize  + 3) * mat.getI() - planeSize * mat.getJ(),
					   *it - (planeSize + 3) * mat.getI() - planeSize * mat.getJ(),
					   col,
					   *driver);

		CDRU::drawLine(*it + planeSize  * mat.getI() + (planeSize + 3) * mat.getJ(),
					   *it + planeSize * mat.getI() - (planeSize + 3) * mat.getJ(),
					   col,
					   *driver);
		CDRU::drawLine(*it - planeSize  * mat.getI() + (planeSize + 3) * mat.getJ(),
					   *it - planeSize * mat.getI() - (planeSize + 3) * mat.getJ(),
					   col,
					   *driver);
	}
}



void CPSZonePlane::resize(uint32 size)
{
	NL_PS_FUNC(CPSZonePlane_resize)
	nlassert(size < (1 << 16));
	_Normal.resize(size);
}


void CPSZonePlane::newElement(const CPSEmitterInfo &info)
{
	NL_PS_FUNC(CPSZonePlane_newElement)
	nlassert(_Normal.getSize() != _Normal.getMaxSize());
	_Normal.insert(CVector(0, 0, 1));
}


void CPSZonePlane::deleteElement(uint32 index)
{
	NL_PS_FUNC(CPSZonePlane_deleteElement)
	_Normal.remove(index);
}


void CPSZonePlane::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter)
{
	NL_PS_FUNC(CPSZonePlane_computeCollisions)
	MINI_TIMER(PSStatsZonePlane)
	// for each target, we must check whether they are going through the plane
	// if so they must bounce
	TPSAttribVector::const_iterator planePosIt, planePosEnd, normalIt;
	CPSCollisionInfo ci;
	// cycle through the planes
	planePosEnd = _Owner->getPos().end();
	for (planePosIt = _Owner->getPos().begin(), normalIt = _Normal.begin(); planePosIt != planePosEnd; ++planePosIt, ++normalIt)
	{

		// we must setup the plane in the good basis
		const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner);
		const float epsilon = 0.5f * PSCollideEpsilon;
		NLMISC::CPlane p;
		p.make(m.mulVector(*normalIt), m * (*planePosIt));
		// deals with each particle
		const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex;
		const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize();
		const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex;
		while (itPosBefore != itPosBeforeEnd)
		{
			float posSide = p * *itPosBefore;
			float negSide = p * *itPosAfter;
			if (posSide >= - epsilon && negSide <= epsilon)
			{
				float alpha;
				if (fabsf(posSide - negSide) > std::numeric_limits<float>::min())
				{
					alpha = posSide / (posSide - negSide);
				}
				else
				{
					alpha = 0.f;
				}
				CVector startEnd = alpha * (*itPosAfter - *itPosBefore);
				ci.Dist = startEnd.norm();
				// we translate the particle from an epsilon so that it won't get hooked to the plane
				ci.NewPos = *itPosBefore  + startEnd + PSCollideEpsilon * p.getNormal();
				const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)];
				ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * p.getNormal()) * p.getNormal());
				ci.CollisionZone = this;
				CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
			}
			++ itPosBefore;
			++ itPosAfter;
		}
	}

}

void CPSZonePlane::setMatrix(uint32 index, const CMatrix &m)
{
	NL_PS_FUNC(CPSZonePlane_setMatrix)
	nlassert(index < _Normal.getSize());
	_Normal[index] = m.getK();
	_Owner->getPos()[index] = m.getPos();
}

CMatrix CPSZonePlane::getMatrix(uint32 index) const
{
	NL_PS_FUNC(CPSZonePlane_getMatrix)
	return buildBasis(index);
}


CVector CPSZonePlane::getNormal(uint32 index)
{
	NL_PS_FUNC(CPSZonePlane_getNormal)
	return _Normal[index];
}
void CPSZonePlane::setNormal(uint32 index, CVector n)
{
	NL_PS_FUNC(CPSZonePlane_setNormal)
	_Normal[index] = n;
}




void CPSZonePlane::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	NL_PS_FUNC(CPSZonePlane_serial)
	f.serialVersion(1);
	CPSZone::serial(f);
	f.serial(_Normal);
}



///////////////////////////
// sphere implementation //
///////////////////////////

void CPSZoneSphere::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter)
{
	NL_PS_FUNC(CPSZoneSphere_computeCollisions)
	MINI_TIMER(PSStatsZoneSphere)
	// for each target, we must check whether they are going through the plane
	// if so they must bounce
	TPSAttribRadiusPair::const_iterator radiusIt = _Radius.begin();
	TPSAttribVector::const_iterator spherePosIt, spherePosEnd;
	CPSCollisionInfo ci;
	float rOut, rIn;
	// cycle through the spheres
	spherePosEnd = _Owner->getPos().end();
	for (spherePosIt = _Owner->getPos().begin(), radiusIt = _Radius.begin(); spherePosIt != spherePosEnd; ++spherePosIt, ++radiusIt)
	{
		// we must setup the sphere in the good basis
		const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner);
		CVector center = m * *spherePosIt;
		// deals with each particle
		const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex;
		const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize();
		const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex;
		while (itPosBefore != itPosBeforeEnd)
		{
			// check whether the located is going through the sphere
			// we don't use raytracing for now because it is too slow ...
			rOut = (*itPosBefore - center) * (*itPosBefore - center);
			// initial position outside the sphere ?
			if (rOut > radiusIt->R2)
			{
				rIn = (*itPosAfter - center) * (*itPosAfter - center);
				const CVector &pos = *itPosBefore;
				const CVector &dest = *itPosAfter;
				const CVector D = dest - pos;
				// final position inside the sphere ?
				if ( rIn <= radiusIt->R2)
				{
					// discriminant of the intersection equation
					const float b = 2.f * (pos * D - D * center), a = D * D
								, c = (pos * pos) + (center * center) - 2.f * (pos * center) - radiusIt->R2;
					float d = b * b - 4 * a * c;
					if (d > 0.f)
					{
						d = sqrtf(d);
						// roots of the equation, we take the smallest
						const float r1 = .5f * (-b + 2.f * d) * a,
									r2 = .5f * (-b - 2.f * d) * a;
						const float  r = std::min(r1, r2);
						// collision point
						const CVector C = pos  + r * D;
						const float alpha = ((C - pos) * D) * a;
						const CVector startEnd = alpha * (dest - pos);
						CVector normal = C - center;
						normal = normal * (1.f / radiusIt->R);
						ci.Dist = startEnd.norm();
						// we translate the particle from an epsilon so that it won't get hooked to the sphere
						ci.NewPos = pos  + startEnd + PSCollideEpsilon * normal;
						const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)];
						ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * normal) * normal);
						ci.CollisionZone = this;
						CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
					}
				}
			}
			++ itPosBefore;
			++ itPosAfter;
		}
	}

}



void CPSZoneSphere::show()
{
	NL_PS_FUNC(CPSZoneSphere_show)

	CPSLocated *loc;
	uint32 index;
	CPSLocatedBindable *lb;
	_Owner->getOwner()->getCurrentEditedElement(loc, index, lb);


	TPSAttribRadiusPair::const_iterator radiusIt = _Radius.begin();
	TPSAttribVector::const_iterator posIt = _Owner->getPos().begin(), endPosIt = _Owner->getPos().end();
	setupDriverModelMatrix();
	for (uint k = 0; posIt != endPosIt; ++posIt, ++radiusIt, ++k)
	{
		const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k  ? CRGBA::Red : CRGBA(127, 127, 127));
		CPSUtil::displaySphere(*getDriver(), radiusIt->R, *posIt, 5, col);
	}
}

void CPSZoneSphere::setMatrix(uint32 index, const CMatrix &m)
{
	NL_PS_FUNC(CPSZoneSphere_setMatrix)
	nlassert(index < _Radius.getSize());

	// compute new pos
	_Owner->getPos()[index] = m.getPos();

}


CMatrix CPSZoneSphere::getMatrix(uint32 index) const
{
	NL_PS_FUNC(CPSZoneSphere_getMatrix)
	nlassert(index < _Radius.getSize());
	CMatrix m;
	m.identity();
	m.translate(_Owner->getPos()[index]);
	return m;
}

void CPSZoneSphere::setScale(uint32 k, float scale)
{
	NL_PS_FUNC(CPSZoneSphere_setScale)
	_Radius[k].R = scale;
	_Radius[k].R2 = scale * scale;
}
CVector CPSZoneSphere::getScale(uint32 k) const
{
	NL_PS_FUNC(CPSZoneSphere_getScale)
	return CVector(_Radius[k].R, _Radius[k].R, _Radius[k].R);
}


void CPSZoneSphere::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	NL_PS_FUNC(CPSZoneSphere_serial)
	f.serialVersion(1);
	CPSZone::serial(f);
	f.serial(_Radius);
}

void CPSZoneSphere::resize(uint32 size)
{
	NL_PS_FUNC(CPSZoneSphere_resize)
	nlassert(size < (1 << 16));
	_Radius.resize(size);
}

void CPSZoneSphere::newElement(const CPSEmitterInfo &info)
{
	NL_PS_FUNC(CPSZoneSphere_newElement)
	CRadiusPair rp;
	rp.R = rp.R2 = 1.f;
	nlassert(_Radius.getSize() != _Radius.getMaxSize());
	_Radius.insert(rp);
}

void CPSZoneSphere::deleteElement(uint32 index)
{
	NL_PS_FUNC(CPSZoneSphere_deleteElement)
	_Radius.remove(index);
}


////////////////////////////////
// CPSZoneDisc implementation //
////////////////////////////////
void CPSZoneDisc::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter)
{
	NL_PS_FUNC(CPSZoneDisc_computeCollisions)
	MINI_TIMER(PSStatsZoneDisc)
	// for each target, we must check whether they are going through the disc
	// if so they must bounce
	TPSAttribVector::const_iterator discPosIt, discPosEnd, normalIt;
	TPSAttribRadiusPair::const_iterator radiusIt;
	CPSCollisionInfo ci;
	// the square of radius at the hit point
	float hitRadius2;
	// alpha is the ratio that gives the percent of endPos - startPos that hit the disc
	CVector center;
	// cycle through the disc
	discPosEnd = _Owner->getPos().end();
	for (discPosIt = _Owner->getPos().begin(), radiusIt = _Radius.begin(), normalIt = _Normal.begin(); discPosIt != discPosEnd; ++discPosIt, ++normalIt, ++radiusIt)
	{
		// we must setup the disc in the good basis
		const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner);
		NLMISC::CPlane p;
		center = m * (*discPosIt);
		p.make(m.mulVector(*normalIt), center);

		// deals with each particle
		const float epsilon = 0.5f * PSCollideEpsilon;

		// deals with each particle
		const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex;
		const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize();
		const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex;
		while (itPosBefore != itPosBeforeEnd)
		{
			float posSide = p * *itPosBefore;
			float negSide = p * *itPosAfter;
			if (posSide >= - epsilon && negSide <= epsilon)
			{
				float alpha;
				if (fabsf(posSide - negSide) > std::numeric_limits<float>::min())
				{
					alpha = posSide / (posSide - negSide);
				}
				else
				{
					alpha = 0.f;
				}
				CVector startEnd = alpha * (*itPosAfter - *itPosBefore);
				ci.Dist = startEnd.norm();
				// we translate the particle from an epsilon so that it won't get hooked to the disc
				ci.NewPos = *itPosBefore  + startEnd + PSCollideEpsilon * p.getNormal();
				// now, check the collision pos against radius
				hitRadius2 = (ci.NewPos - center) * (ci.NewPos - center);
				if (hitRadius2 < radiusIt->R2) // check collision against disc
				{
					const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)];
					ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * p.getNormal()) * p.getNormal());
					ci.CollisionZone = this;
					CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
				}

			}
			++ itPosBefore;
			++ itPosAfter;
		}
	}
}

void CPSZoneDisc::show()
{
	NL_PS_FUNC(CPSZoneDisc_show)
	TPSAttribRadiusPair::const_iterator radiusIt = _Radius.begin();
	TPSAttribVector::const_iterator posIt = _Owner->getPos().begin(), endPosIt = _Owner->getPos().end()
									, normalIt = _Normal.begin();
	setupDriverModelMatrix();
	CMatrix mat;



	CPSLocated *loc;
	uint32 index;
	CPSLocatedBindable *lb;
	_Owner->getOwner()->getCurrentEditedElement(loc, index, lb);



	for (uint k = 0; posIt != endPosIt; ++posIt, ++radiusIt, ++normalIt, ++k)
	{
		const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k  ? CRGBA::Red : CRGBA(127, 127, 127));
		CPSUtil::buildSchmidtBasis(*normalIt, mat);
		CPSUtil::displayDisc(*getDriver(), radiusIt->R, *posIt, mat, 32, col);

		mat.setPos(*posIt);
		CPSUtil::displayBasis(getDriver() ,getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager());
		setupDriverModelMatrix();
	}
}






void CPSZoneDisc::setMatrix(uint32 index, const CMatrix &m)
{
	NL_PS_FUNC(CPSZoneDisc_setMatrix)
	nlassert(index < _Radius.getSize());
	// compute new pos
	_Owner->getPos()[index] = m.getPos();
	// compute new normal
	_Normal[index] = m.getK();
}

CMatrix CPSZoneDisc::getMatrix(uint32 index) const
{
	NL_PS_FUNC(CPSZoneDisc_getMatrix)
	CMatrix m, b;
	m.translate(_Owner->getPos()[index]);
	CPSUtil::buildSchmidtBasis(_Normal[index], b);
	m = m * b;
	return m;
}

CVector CPSZoneDisc::getNormal(uint32 index)
{
	NL_PS_FUNC(CPSZoneDisc_getNormal)
	return _Normal[index];
}
void CPSZoneDisc::setNormal(uint32 index, CVector n)
{
	NL_PS_FUNC(CPSZoneDisc_setNormal)
	_Normal[index] = n;
}

void CPSZoneDisc::setScale(uint32 k, float scale)
{
	NL_PS_FUNC(CPSZoneDisc_setScale)
	_Radius[k].R = scale;
	_Radius[k].R2 = scale * scale;
}

CVector CPSZoneDisc::getScale(uint32 k) const
{
	NL_PS_FUNC(CPSZoneDisc_getScale)
	return CVector(_Radius[k].R, _Radius[k].R, _Radius[k].R);
}


void CPSZoneDisc::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	NL_PS_FUNC(CPSZoneDisc_serial)
	f.serialVersion(1);
	CPSZone::serial(f);
	f.serial(_Normal);
	f.serial(_Radius);
}

void CPSZoneDisc::resize(uint32 size)
{
	NL_PS_FUNC(CPSZoneDisc_resize)
	nlassert(size < (1 << 16));
	_Radius.resize(size);
	_Normal.resize(size);
}

void CPSZoneDisc::newElement(const CPSEmitterInfo &info)
{
	NL_PS_FUNC(CPSZoneDisc_newElement)
	CRadiusPair rp;
	rp.R = rp.R2 = 1.f;
	nlassert(_Radius.getSize() != _Radius.getMaxSize());
	_Radius.insert(rp);
	_Normal.insert(CVector::K);
}

void CPSZoneDisc::deleteElement(uint32 index)
{
	NL_PS_FUNC(CPSZoneDisc_deleteElement)
	_Radius.remove(index);
	_Normal.remove(index);
}


////////////////////////////////////
// CPSZoneCylinder implementation //
////////////////////////////////////


/*
void CPSZoneCylinder::performMotion(TAnimationTime ellapsedTime)
{
	TPSAttribVector::const_iterator dimIt = _Dim.begin();
	CPSAttrib<CPlaneBasis>::const_iterator basisIt = _Basis.begin();
	TPSAttribVector::const_iterator cylinderPosIt, cylinderPosEnd, targetPosIt, targetPosEnd;
	CVector dest;
	CPSCollisionInfo ci;
	CVector startEnd;
	uint32 k;
	const TPSAttribVector *speedAttr;



	for (TTargetCont::iterator it = _Targets.begin(); it != _Targets.end(); ++it)
	{

		speedAttr = &((*it)->getSpeed());


		// cycle through the cylinders

		cylinderPosEnd = _Owner->getPos().end();
		for (cylinderPosIt = _Owner->getPos().begin(); cylinderPosIt != cylinderPosEnd
				; ++cylinderPosIt, ++dimIt, ++basisIt)
		{

			// we must setup the cylinder in the good basis

			const CMatrix &m = CPSLocated::getConversionMatrix(*it, this->_Owner);



			// compute the new center pos
			CVector center = m * *cylinderPosIt;

			// compute a basis for the cylinder
			CVector I = m.mulVector(basisIt->X);
			CVector J = m.mulVector(basisIt->Y);
			CVector K = m.mulVector(basisIt->X ^ basisIt->Y);


			// the pos projected (and scale) over the plane basis of the cylinder, the pos minus the center
			CVector projectedPos, tPos;

			// the same, but with the final position
			CVector destProjectedPos, destTPos;


			// deals with each particle
			targetPosEnd = (*it)->getPos().end();
			for (targetPosIt = (*it)->getPos().begin(), k = 0; targetPosIt != targetPosEnd; ++targetPosIt, ++k)
			{
				const CVector &speed = (*speedAttr)[k];
				const CVector &pos = *targetPosIt;



				// check whether current pos was outside the cylinder


				tPos = pos - center;
				projectedPos = (1 / dimIt->x) * (I * tPos) * I + (1 / dimIt->y)  * (J * tPos) * J;

				if (!
					 (
						((tPos * K) < dimIt->z)
						&& ((tPos * K) > -dimIt->z)
						&& (projectedPos * projectedPos < 1.f)
					 )
				   )
				{
					dest = pos + ellapsedTime *  speed;
					destTPos = dest - center;
					destProjectedPos = (1.f / dimIt->x) * (I * tPos) * I + (1.f / dimIt->y)  * (J * tPos) * J;

					// test whether the new position is inside the cylinder


					if (!
						 (
							((destTPos * K) < dimIt->z)
							&& ((destTPos * K) > -dimIt->z)
							&& (destProjectedPos * destProjectedPos < 1.f)
						 )
					   )
					{
						// now, detect the closest hit point (the smallest alpha, with alpha, the percent of the move vector
						// to reach the hit point)

						const float epsilon = 10E-6f;

						float alphaTop, alphaBottom, alphaCyl;

						const float denum = (dest - pos) * K;

						// top plane

						if (fabs(denum) < epsilon)
						{
							alphaTop = (dimIt->z - (tPos * K)) / denum;
							if (alphaTop < 0.f) alphaTop = 1.f;
						}
						else
						{
							alphaTop = 1.f;
						}

						// bottom plane

						if (fabs(denum) < epsilon)
						{
							alphaBottom = (- dimIt->z - (tPos * K)) / denum;
							if (alphaBottom < 0.f) alphaBottom = 1.f;
						}
						else
						{
							alphaBottom = 1.f;
						}

						// cylinder

						//expressed the src and dest positions in the cylinder basis

						const float ox = tPos * I, oy = tPos * J, dx = (destTPos - tPos) * I, dy = (destTPos - tPos) * J;

						// coefficients of the equation : a * alpha ^ 2 + b * alpha + c = 0
						const float a = (dx * dx) / (dimIt->x * dimIt->x)
								  + (dy * dy) / (dimIt->y * dimIt->y);
						const float b = 2.f * (ox * dx) / (dimIt->x * dimIt->x)
								  + (oy * dy) / (dimIt->y * dimIt->y);
						const float c = ox * ox + oy * oy - 1;

						// discriminant
						const float delta = b * b - 4.f * a * c;

						if (delta < epsilon)
						{
							alphaCyl = 1.f;
						}
						else
						{
							const float deltaRoot = sqrtf(delta);
							const float r1 = (- b - deltaRoot) / (2.f / a);
							const float r2 = (- b - deltaRoot) / (2.f / a);

							if (r1 < 0.f) alphaCyl = r2;
								else if (r2 < 0.f) alphaCyl = r1;
									else alphaCyl = r1 < r2 ? r1 : r2;
						}


						// now, choose the minimum positive dist

						if (alphaTop < alphaBottom && alphaTop < alphaCyl)
						{
							// collision with the top plane
							CVector startEnd = alphaTop * (dest - pos);
							ci.newPos = pos + startEnd + PSCollideEpsilon * K;
							ci.dist = startEnd.norm();
							ci.newSpeed = (-2.f * (speed * K)) * K + speed;
							ci.collisionZone = this;

							(*it)->collisionUpdate(ci, k);
						}
						else
							if (alphaBottom < alphaCyl)
							{
								// collision with the bottom plane
								CVector startEnd = alphaBottom * (dest - pos);
								ci.newPos = pos + startEnd - PSCollideEpsilon * K;
								ci.dist = startEnd.norm();
								ci.newSpeed = (-2.f * (speed * K)) * K + speed;
								ci.collisionZone = this;


								(*it)->collisionUpdate(ci, k);
							}
							else
							{
								// collision with the cylinder

								CVector startEnd = alphaCyl * (dest - pos);

								// normal at the hit point. It is the gradient of the implicit equation x^2 / a^2 + y^2 / b^2 - R^ 2=  0
								// so we got unormalized n =  (2 x / a ^ 2, 2 y / b ^ 2, 0) in the basis of the cylinder
								// As we'll normalize it, we don't need  the 2 factor

								float px = ox + alphaCyl * dx;
								float py = oy + alphaCyl * dy;

								CVector normal = px / (dimIt->x * dimIt->x) * I + py / (dimIt->y * dimIt->y) * J;
								normal.normalize();

								ci.newPos = pos + startEnd - PSCollideEpsilon * normal;
								ci.dist = startEnd.norm();
								ci.newSpeed = (-2.f * (speed * normal)) * normal + speed;
								ci.collisionZone = this;

								(*it)->collisionUpdate(ci, k);
							}

					}
				}
			}
		}
	}
}
*/


void CPSZoneCylinder::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter)
{
	NL_PS_FUNC(CPSZoneCylinder_computeCollisions)
	MINI_TIMER(PSStatsZoneCylinder)
	TPSAttribVector::const_iterator dimIt;
	CPSAttrib<CPlaneBasis>::const_iterator basisIt;
	TPSAttribVector::const_iterator cylinderPosIt, cylinderPosEnd;
	CPSCollisionInfo ci;
	// cycle through the cylinders
	cylinderPosEnd = _Owner->getPos().end();
	for (cylinderPosIt = _Owner->getPos().begin(), basisIt = _Basis.begin(),  dimIt = _Dim.begin(); cylinderPosIt != cylinderPosEnd; ++cylinderPosIt, ++dimIt, ++basisIt)
	{
		// we must setup the cylinder in the good basis
		const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner);
		// compute the new center pos
		CVector center = m * *cylinderPosIt;
		// compute a basis for the cylinder
		CVector I = m.mulVector(basisIt->X);
		CVector J = m.mulVector(basisIt->Y);
		CVector K = m.mulVector(basisIt->X ^ basisIt->Y);
		// the pos projected (and scale) over the plane basis of the cylinder, the pos minus the center
		CVector projectedPos, tPos;
		// the same, but with the final position
		CVector destProjectedPos, destTPos;
		// deals with each particle
		// deals with each particle
		const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex;
		const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize();
		const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex;
		while (itPosBefore != itPosBeforeEnd)
		{
			const CVector &pos = *itPosBefore;
			// check whether current pos was outside the cylinder
			tPos = pos - center;
			projectedPos = (1 / dimIt->x) * (I * tPos) * I + (1 / dimIt->y)  * (J * tPos) * J;
			if (!
				 (
					((tPos * K) < dimIt->z)
					&& ((tPos * K) > -dimIt->z)
					&& (projectedPos * projectedPos < 1.f)
				 )
			   )
			{
				const CVector &dest = *itPosAfter;
				destTPos = dest - center;
				destProjectedPos = (1.f / dimIt->x) * (I * destTPos) * I + (1.f / dimIt->y)  * (J * destTPos) * J;
				// test whether the new position is inside the cylinder
				if (
					((destTPos * K) < dimIt->z)
					&& ((destTPos * K) > -dimIt->z)
					&& (destProjectedPos * destProjectedPos < 1.f)
				   )
				{
					// now, detect the closest hit point (the smallest alpha, with alpha, the percent of the move vector
					// to reach the hit point)
					const float epsilon = 10E-3f;
					float alphaTop, alphaBottom, alphaCyl;
					const float denum = (dest - pos) * K;
					// top plane
					if (fabs(denum) < epsilon)
					{
						alphaTop = (dimIt->z - (tPos * K)) / denum;
						if (alphaTop < 0.f) alphaTop = 1.f;
					}
					else
					{
						alphaTop = 1.f;
					}
					// bottom plane
					if (fabs(denum) < epsilon)
					{
						alphaBottom = (- dimIt->z - (tPos * K)) / denum;
						if (alphaBottom < 0.f) alphaBottom = 1.f;
					}
					else
					{
						alphaBottom = 1.f;
					}
					// cylinder
					//expressed the src and dest positions in the cylinder basis
					const float ox = tPos * I, oy = tPos * J, dx = (destTPos - tPos) * I, dy = (destTPos - tPos) * J;
					// coefficients of the equation : a * alpha ^ 2 + b * alpha + c = 0
					const float a = (dx * dx) / (dimIt->x * dimIt->x)
							  + (dy * dy) / (dimIt->y * dimIt->y);
					const float b = 2.f * ((ox * dx) / (dimIt->x * dimIt->x)
							  + (oy * dy) / (dimIt->y * dimIt->y));
					const float c = (ox * ox) / (dimIt->x * dimIt->x) + (oy * oy) / (dimIt->y * dimIt->y)  - 1;
					// discriminant
					const float delta = b * b - 4.f * a * c;

					if (delta < epsilon)
					{
						alphaCyl = 1.f;
					}
					else
					{
						const float deltaRoot = sqrtf(delta);
						const float r1 = (- b + 2.f * deltaRoot) / (2.f * a);
						const float r2 = (- b - 2.f * deltaRoot) / (2.f * a);

						if (r1 < 0.f) alphaCyl = r2;
							else if (r2 < 0.f) alphaCyl = r1;
								else alphaCyl = r1 < r2 ? r1 : r2;

								if (alphaCyl < 0.f) alphaCyl = 1.f;
					}
					const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)];
					// now, choose the minimum positive dist
					if (alphaTop < alphaBottom && alphaTop < alphaCyl)
					{
						// collision with the top plane
						CVector startEnd = alphaTop * (dest - pos);
						ci.NewPos = pos + startEnd + PSCollideEpsilon * K;
						ci.Dist = startEnd.norm();
						ci.NewSpeed = (-2.f * (speed * K)) * K + speed;
						ci.CollisionZone = this;
						CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
					}
					else
						if (alphaBottom < alphaCyl)
						{
							// collision with the bottom plane
							CVector startEnd = alphaBottom * (dest - pos);
							ci.NewPos = pos + startEnd - PSCollideEpsilon * K;
							ci.Dist = startEnd.norm();
							ci.NewSpeed = (-2.f * (speed * K)) * K + speed;
							ci.CollisionZone = this;
							CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
						}
						else
						{
							// collision with the cylinder
							CVector startEnd = alphaCyl * (dest - pos);
							// normal at the hit point. It is the gradient of the implicit equation x^2 / a^2 + y^2 / b^2 - R^ 2=  0
							// so we got unormalized n =  (2 x / a ^ 2, 2 y / b ^ 2, 0) in the basis of the cylinder
							// As we'll normalize it, we don't need  the 2 factor
							float px = ox + alphaCyl * dx;
							float py = oy + alphaCyl * dy;
							CVector normal = px / (dimIt->x * dimIt->x) * I + py / (dimIt->y * dimIt->y) * J;
							normal.normalize();
							ci.NewPos = pos + startEnd + PSCollideEpsilon * normal;
							ci.Dist = startEnd.norm();
							ci.NewSpeed = (-2.f * (speed * normal)) * normal + speed;
							ci.CollisionZone = this;
							CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
						}

				}
			}
			++ itPosBefore;
			++ itPosAfter;
		}
	}
}

void CPSZoneCylinder::show()
{
	NL_PS_FUNC(CPSZoneCylinder_show)
	TPSAttribVector::const_iterator dimIt = _Dim.begin()
									,posIt = _Owner->getPos().begin()
									, endPosIt = _Owner->getPos().end();

	CPSAttrib<CPlaneBasis>::const_iterator basisIt = _Basis.begin();

	setupDriverModelMatrix();
	CMatrix mat;



	CPSLocated *loc;
	uint32 index;
	CPSLocatedBindable *lb;
	_Owner->getOwner()->getCurrentEditedElement(loc, index, lb);


	for (uint32 k = 0; posIt != endPosIt; ++posIt, ++dimIt, ++basisIt, ++k)
	{
		mat.setRot(basisIt->X, basisIt->Y, basisIt->X ^ basisIt->Y);
		mat.setPos(CVector::Null);

		const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k  ? CRGBA::Red : CRGBA(127, 127, 127));


		CPSUtil::displayCylinder(*getDriver(), *posIt, mat, *dimIt, 32, col);

		mat.setPos(*posIt);
		CPSUtil::displayBasis(getDriver() ,getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager());
		setupDriverModelMatrix();

	}
}



void CPSZoneCylinder::setMatrix(uint32 index, const CMatrix &m)
{
	NL_PS_FUNC(CPSZoneCylinder_setMatrix)
	// transform the basis
	_Basis[index].X = m.getI();
	_Basis[index].Y = m.getJ();

	// compute new pos
	_Owner->getPos()[index] = m.getPos();


}



CMatrix CPSZoneCylinder::getMatrix(uint32 index) const
{
	NL_PS_FUNC(CPSZoneCylinder_getMatrix)
	CMatrix m;
	m.setRot(_Basis[index].X, _Basis[index].Y, _Basis[index].X ^_Basis[index].Y);
	m.setPos(_Owner->getPos()[index]);
	return m;
}


void CPSZoneCylinder::setScale(uint32 k, float scale)
{
	NL_PS_FUNC(CPSZoneCylinder_setScale)
	_Dim[k] = CVector(scale, scale, scale);
}

CVector CPSZoneCylinder::getScale(uint32 k) const
{
	NL_PS_FUNC(CPSZoneCylinder_getScale)
	return _Dim[k];
}

void CPSZoneCylinder::setScale(uint32 index, const CVector &s)
{
	NL_PS_FUNC(CPSZoneCylinder_setScale)
	_Dim[index] = s;
}


void CPSZoneCylinder::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	NL_PS_FUNC(CPSZoneCylinder_serial)
	f.serialVersion(1);
	CPSZone::serial(f);
	f.serial(_Basis);
	f.serial(_Dim);
}



void CPSZoneCylinder::resize(uint32 size)
{
	NL_PS_FUNC(CPSZoneCylinder_resize)
	nlassert(size < (1 << 16));
	_Basis.resize(size);
	_Dim.resize(size);
}

void CPSZoneCylinder::newElement(const CPSEmitterInfo &info)
{
	NL_PS_FUNC(CPSZoneCylinder_newElement)
	_Basis.insert(CPlaneBasis(CVector::K));
	_Dim.insert(CVector(1, 1, 1));
}

void CPSZoneCylinder::deleteElement(uint32 index)
{
	NL_PS_FUNC(CPSZoneCylinder_deleteElement)
	_Basis.remove(index);
	_Dim.remove(index);
}


//////////////////////////////////////////////
//	implementation of CPSZoneRectangle      //
//////////////////////////////////////////////

void CPSZoneRectangle::computeCollisions(CPSLocated &target, uint firstInstanceIndex, const NLMISC::CVector *posBefore, const NLMISC::CVector *posAfter)
{
	NL_PS_FUNC(CPSZoneRectangle_computeCollisions)
	MINI_TIMER(PSStatsZoneRectangle)
	// for each target, we must check whether they are going through the rectangle
	// if so they must bounce
	TPSAttribVector::const_iterator rectanglePosIt, rectanglePosEnd;
	CPSAttrib<CPlaneBasis>::const_iterator basisIt;
	TPSAttribFloat::const_iterator widthIt, heightIt;
	CPSCollisionInfo ci;
	// alpha is the ratio that gives the percent of endPos - startPos that hit the rectangle
	basisIt = _Basis.begin();
	heightIt = _Height.begin();
	widthIt = _Width.begin();
	rectanglePosEnd = _Owner->getPos().end();
	for (rectanglePosIt = _Owner->getPos().begin(); rectanglePosIt != rectanglePosEnd; ++rectanglePosIt, ++basisIt, ++widthIt, ++heightIt)
	{
		// we must setup the rectangle in the good basis
		const CMatrix &m = CPSLocated::getConversionMatrix(&target, this->_Owner);
		NLMISC::CPlane p;
		CVector center = m * (*rectanglePosIt);
		const CVector X = m.mulVector(basisIt->X);
		const CVector Y = m.mulVector(basisIt->Y);
		p.make(X ^ Y, center);
		// deals with each particle
		const float epsilon = 0.5f * PSCollideEpsilon;
		const NLMISC::CVector *itPosBefore = posBefore + firstInstanceIndex;
		const NLMISC::CVector *itPosBeforeEnd = posBefore + target.getSize();
		const NLMISC::CVector *itPosAfter = posAfter + firstInstanceIndex;
		while (itPosBefore != itPosBeforeEnd)
		{
			float posSide = p * *itPosBefore;
			float negSide = p * *itPosAfter;
			if (posSide >= - epsilon && negSide <= epsilon)
			{
				float alpha;
				if (fabsf(posSide - negSide) > std::numeric_limits<float>::min())
				{
					alpha = posSide / (posSide - negSide);
				}
				else
				{
					alpha = 0.f;
				}
				CVector startEnd = alpha * (*itPosAfter - *itPosBefore);
				ci.Dist = startEnd.norm();
				// we translate the particle from an epsilon so that it won't get hooked to the rectangle
				ci.NewPos = *itPosBefore + startEnd;
				// tmp
				if ( fabs( (ci.NewPos - center) * X ) < *widthIt && fabs( (ci.NewPos - center) * Y ) < *heightIt) // check collision against rectangle
				{
					ci.NewPos += PSCollideEpsilon * p.getNormal();
					const CVector &speed = target.getSpeed()[(uint32)(itPosBefore - posBefore)];
					ci.NewSpeed = _BounceFactor * (speed - 2.0f * (speed * p.getNormal()) * p.getNormal());
					ci.CollisionZone = this;
					CPSLocated::_Collisions[itPosBefore - posBefore].update(ci);
				}
			}
			++ itPosBefore;
			++ itPosAfter;
		}
	}

}


void CPSZoneRectangle::show()
{
	NL_PS_FUNC(CPSZoneRectangle_show)
	nlassert(_Owner);
	const uint size = _Owner->getSize();
	if (!size) return;
	setupDriverModelMatrix();
	CMatrix mat;

	CPSLocated *loc;
	uint32 index;
	CPSLocatedBindable *lb;
	_Owner->getOwner()->getCurrentEditedElement(loc, index, lb);

	for (uint k = 0; k < size; ++k)
	{
		const CVector &I = _Basis[k].X;
		const CVector &J = _Basis[k].Y;
		mat.setRot(I, J , I ^J);
		mat.setPos(_Owner->getPos()[k]);
		CPSUtil::displayBasis(getDriver(), getLocalToWorldMatrix(), mat, 1.f, *getFontGenerator(), *getFontManager());
		setupDriverModelMatrix();

		const CRGBA col = ((lb == NULL || this == lb) && loc == _Owner && index == k  ? CRGBA::Red : CRGBA(127, 127, 127));



		const CVector &pos = _Owner->getPos()[k];
		CPSUtil::display3DQuad(*getDriver(), pos + I * _Width[k] + J * _Height[k]
										   , pos + I * _Width[k] - J * _Height[k]
										   , pos - I * _Width[k] - J * _Height[k]
										   , pos - I * _Width[k] + J * _Height[k], col);
	}
}

void CPSZoneRectangle::setMatrix(uint32 index, const CMatrix &m)
{
	NL_PS_FUNC(CPSZoneRectangle_setMatrix)
	nlassert(_Owner);

	_Owner->getPos()[index] = m.getPos();
	_Basis[index].X = m.getI();
	_Basis[index].Y = m.getJ();
}


CMatrix CPSZoneRectangle::getMatrix(uint32 index) const
{
	NL_PS_FUNC(CPSZoneRectangle_getMatrix)
	nlassert(_Owner);
	CMatrix m;
	m.setRot(_Basis[index].X, _Basis[index].Y, _Basis[index].X ^ _Basis[index].Y);
	m.setPos(_Owner->getPos()[index]);
	return m;
}

void CPSZoneRectangle::setScale(uint32 index, float scale)
{
	NL_PS_FUNC(CPSZoneRectangle_setScale)
	_Width[index] = scale;
	_Height[index] = scale;
}
void CPSZoneRectangle::setScale(uint32 index, const CVector &s)
{
	NL_PS_FUNC(CPSZoneRectangle_setScale)
	_Width[index] = s.x;
	_Height[index] = s.y;
}
CVector CPSZoneRectangle::getScale(uint32 index) const
{
	NL_PS_FUNC(CPSZoneRectangle_getScale)
	return CVector(_Width[index], _Height[index], 1.f);
}



void CPSZoneRectangle::serial(NLMISC::IStream &f) throw(NLMISC::EStream)
{
	NL_PS_FUNC(CPSZoneRectangle_IStream )
	f.serialVersion(1);
	CPSZone::serial(f);
	f.serial(_Basis);
	f.serial(_Width);
	f.serial(_Height);
}


void CPSZoneRectangle::resize(uint32 size)
{
	NL_PS_FUNC(CPSZoneRectangle_resize)
	nlassert(size < (1 << 16));
	_Basis.resize(size);
	_Width.resize(size);
	_Height.resize(size);
}

void CPSZoneRectangle::newElement(const CPSEmitterInfo &info)
{
	NL_PS_FUNC(CPSZoneRectangle_newElement)
	_Basis.insert(CPlaneBasis(CVector::K));
	_Width.insert(1.f);
	_Height.insert(1.f);
}

void CPSZoneRectangle::deleteElement(uint32 index)
{
	NL_PS_FUNC(CPSZoneRectangle_deleteElement)
	_Basis.remove(index);
	_Width.remove(index);
	_Height.remove(index);
}



} // NL3D