//Jacek Matulewski, e-mail: jacek@fizyka.umk.pl

#ifndef UKLADYPUNKTOWMATERIALNYCH2_H
#define UKLADYPUNKTOWMATERIALNYCH2_H

#include "PunktMaterialny.h"

class Pudlo : public ObszarZabroniony
{
	private:
		double minX,maxX,minY,maxY,minZ,maxZ;

	public:
		Wektor PobierzSrodek()
		{
			return Wektor((minX+maxX)/2.0,(minY+maxY)/2.0,(minZ+maxZ)/2.0);
		}

		Wektor PobierzRozmiar()
		{
			return Wektor(maxX-minX,maxY-minY,maxZ-minZ);
		}

		bool CzyWObszarzeZabronionym(Wektor polozenie,Wektor poprzedniePolozenie,double margines,Wektor* normalna)
		{			
			double _minX=minX+margines,_maxX=maxX-margines;
			double _minY=minY+margines,_maxY=maxY-margines;
			double _minZ=minZ+margines,_maxZ=maxZ-margines;
			bool wynik=polozenie.X<_minX || polozenie.X>_maxX || polozenie.Y<_minY || polozenie.Y>_maxY || polozenie.Z<_minZ || polozenie.Z>_maxZ;
			if(wynik && normalna!=NULL)
			{	
				//Wektor przemieszczenie=polozenie-poprzedniePolozenie;
				if(polozenie.X<_minX) normalna->X=1;
				if(polozenie.X>_maxX) normalna->X=-1;
				if(polozenie.Y<_minY) normalna->Y=1;
				if(polozenie.Y>_maxY) normalna->Y=-1;
				if(polozenie.Z<_minZ) normalna->Z=1;
				if(polozenie.Z>_maxZ) normalna->Z=-1;
			}
			return wynik;
		}

		Pudlo(double wspolczynnikOdbicia,double wspolczynnikTarcia,double minX,double maxX,double minY,double maxY,double minZ,double maxZ)
			:ObszarZabroniony(wspolczynnikOdbicia,wspolczynnikTarcia),
			 minX(minX),maxX(maxX),minY(minY),maxY(maxY),minZ(minZ),maxZ(maxZ)
		{	
		}
};

class ZbiorPunktowMaterialnychZeZderzeniami : public ZbiorPunktowMaterialnychZObszaremZabronionym
{
	private:
		double e;

	public:
		ZbiorPunktowMaterialnychZeZderzeniami(int ilosc,double wspolczynnikRestytucji)
			:ZbiorPunktowMaterialnychZObszaremZabronionym(ilosc),
			 e(wspolczynnikRestytucji)
		{			
		}

	private:
		void KolizjeCentralne(double krokCzasowy)
		{
			for(int i=0;i<ilosc;i++)
			{
				PunktMaterialny* punktI=PobierzPunktMaterialny(i);
				double mI=punktI->Masa();
				for(int j=i+1;j<ilosc;j++)
				{										
					PunktMaterialny* punktJ=PobierzPunktMaterialny(j);

					double nastepnaOdleglosc=(punktI->NastepnePolozenie()-punktJ->NastepnePolozenie()).Dlugosc();
					if(nastepnaOdleglosc<=(punktI->Promien()+punktJ->Promien()))
					{						
						double mJ=punktJ->Masa();
						double odwSumyMas=1/(mI+mJ);
			
						Wektor uI=((e+1)*mJ*punktJ->Predkosc()+punktI->Predkosc()*(mI-e*mJ))*odwSumyMas;
						Wektor uJ=((e+1)*mI*punktI->Predkosc()+punktJ->Predkosc()*(mJ-e*mI))*odwSumyMas;
						punktI->UstawPredkosc(uI);
						punktJ->UstawPredkosc(uJ);						
						punktI->PrzygotujRuch(Sila(i),krokCzasowy,algorytmEulera);
						punktJ->PrzygotujRuch(Sila(j),krokCzasowy,algorytmEulera);
					}
				}
			}
		}

		void Kolizje(double krokCzasowy)
		{
			for(int i=0;i<ilosc;i++)
			{
				PunktMaterialny* punktI=PobierzPunktMaterialny(i);
				double mI=punktI->Masa();
				for(int j=i+1;j<ilosc;j++)
				{										
					PunktMaterialny* punktJ=PobierzPunktMaterialny(j);

					double nastepnaOdleglosc=(punktI->NastepnePolozenie()-punktJ->NastepnePolozenie()).Dlugosc();
					if(nastepnaOdleglosc<=(punktI->Promien()+punktJ->Promien()))
					{						
						Wektor n=punktI->Polozenie()-punktJ->Polozenie();
						n.Normuj();

						double mJ=punktJ->Masa();
						double masaZred=1/(1/mI+1/mJ);

						double Dvn=(punktI->Predkosc()-punktJ->Predkosc())*n;

						double J=-masaZred*(e+1)*Dvn;
			
						Wektor uI=punktI->Predkosc()+n*J/mI;
						Wektor uJ=punktJ->Predkosc()-n*J/mJ;
						punktI->UstawPredkosc(uI);
						punktJ->UstawPredkosc(uJ);						
						punktI->PrzygotujRuch(Sila(i),krokCzasowy,algorytmEulera);
						punktJ->PrzygotujRuch(Sila(j),krokCzasowy,algorytmEulera);
					}
				}
			}
		}

	protected:
		void PoPrzygotowaniuRuchu(double krokCzasowy)
		{
			//KolizjeCentralne(krokCzasowy);
			Kolizje(krokCzasowy);
		}
};

#define _USE_MATH_DEFINES
#include <math.h>
#include <time.h>

class ZderzajaceSiePunkty  : public ZbiorPunktowMaterialnychZeZderzeniami
{
	public:
		ZderzajaceSiePunkty(int ilosc,double wspolczynnikRestytucji,Wektor polozenieMin,Wektor polozenieMax,double predkoscMin,double predkoscMax,double masaMin,double masaMax)
			:ZbiorPunktowMaterialnychZeZderzeniami(ilosc,wspolczynnikRestytucji)
		{
			obszarZabroniony=new Pudlo(1,0,1.1*polozenieMin.X,1.1*polozenieMax.X,1.1*polozenieMin.Y,1.1*polozenieMax.Y,-1,1);
			
			srand((unsigned)time(NULL));
			for(int i=0;i<ilosc;++i)
			{
				bool wynikTestuNakrywania=false;
				do
				{
					double polozenieX=(double)rand()/(RAND_MAX+1)*(polozenieMax.X-polozenieMin.X)+polozenieMin.X;
					double polozenieY=(double)rand()/(RAND_MAX+1)*(polozenieMax.Y-polozenieMin.Y)+polozenieMin.Y;
					double polozenieZ=(double)rand()/(RAND_MAX+1)*(polozenieMax.Z-polozenieMin.Z)+polozenieMin.Z;				
					PunktMaterialny* punkt=PobierzPunktMaterialny(i);				
					punkt->UstawPolozenie(Wektor(polozenieX,polozenieY,polozenieZ));
					double predkoscKatPhi=(double)rand()/(RAND_MAX+1)*2*M_PI;					
					double szybkosc=(double)rand()/(RAND_MAX+1)*(predkoscMax-predkoscMin)+predkoscMin;
					punkt->UstawPredkosc(szybkosc*Wektor(cos(predkoscKatPhi),sin(predkoscKatPhi),0));
					double masa=(double)rand()/(RAND_MAX+1)*(masaMax-masaMin)+masaMin;
					punkt->UstawMase(masa);				
					punkt->UstawKolor(i/(float)ilosc,1-i/(float)ilosc,1);

					//test nakladania sfer
					wynikTestuNakrywania=false;
					for(int j=0;j<i;++j)
					{
						PunktMaterialny* punktJ=PobierzPunktMaterialny(j);
						if((punkt->Polozenie()-punktJ->Polozenie()).Dlugosc()<=(punkt->Promien()+punktJ->Promien()))
							wynikTestuNakrywania=true;
					}
				}
				while(wynikTestuNakrywania);
			}						
		}

	protected:
		Wektor Sila(int indeks) const
		{
			return Wektor(0,0,0);
		}
};

class ProblemBilardzisty : public ZbiorPunktowMaterialnychZeZderzeniami
{
	public:
		ProblemBilardzisty(double promien,double parametrZderzenia)
			:ZbiorPunktowMaterialnychZeZderzeniami(2,1)
		{			
			PunktMaterialny* punkt1=PobierzPunktMaterialny(0);
			punkt1->UstawPolozenie(Wektor(-2,parametrZderzenia,0));
			punkt1->UstawPredkosc(Wektor(1,0,0));
			punkt1->UstawMase(1);
			punkt1->UstawPromien(promien);
			punkt1->UstawKolor(0,1,0);

			PunktMaterialny* punkt2=PobierzPunktMaterialny(1);
			punkt2->UstawPolozenie(Wektor(0,0,0));
			punkt2->UstawPredkosc(Wektor(0,0,0));
			punkt2->UstawMase(1);
			punkt2->UstawPromien(promien);
			punkt2->UstawKolor(1,0,0);
		}

	private:
		Wektor Sila(int indeks) const
		{
			return Wektor(0,0,0);
		}
};

class PunktyPoruszajaceSiePionowoIPoziomo : public ZbiorPunktowMaterialnychZeZderzeniami
{
	public:
		PunktyPoruszajaceSiePionowoIPoziomo(int ilosc1,int ilosc2,Wektor polozenieMin,Wektor polozenieMax,double predkoscMin,double predkoscMax,double masaMin,double masaMax)
			:ZbiorPunktowMaterialnychZeZderzeniami(ilosc1+ilosc2,1)
		{
			obszarZabroniony=new Pudlo(1,0,1.1*polozenieMin.X,1.1*polozenieMax.X,1.1*polozenieMin.Y,1.1*polozenieMax.Y,-1,1);

			srand((unsigned)time(NULL));
			for(int grupa=1;grupa<=2;grupa++)
			{				
				int offset=0;
				int ilosc=ilosc1;
				if (grupa==2) 
				{
					offset=ilosc1;
					ilosc=ilosc2;
				}
				for(int i=0;i<ilosc;++i)
				{
					bool wynikTestuNakrywania=false;
					do
					{
						double polozenieX=(double)rand()/(RAND_MAX+1)*(polozenieMax.X-polozenieMin.X)+polozenieMin.X;
						double polozenieY=(double)rand()/(RAND_MAX+1)*(polozenieMax.Y-polozenieMin.Y)+polozenieMin.Y;
						double polozenieZ=(double)rand()/(RAND_MAX+1)*(polozenieMax.Z-polozenieMin.Z)+polozenieMin.Z;						
						PunktMaterialny* punkt=PobierzPunktMaterialny(i+offset);
						punkt->UstawPolozenie(Wektor(polozenieX,polozenieY,polozenieZ));
						double predkoscKatPhi=(double)rand()/(RAND_MAX+1)*2*M_PI;						
						if (grupa==1) predkoscKatPhi=0;
						if (grupa==2) predkoscKatPhi=M_PI/2;
						double szybkosc=(double)rand()/(RAND_MAX+1)*(predkoscMax-predkoscMin)+predkoscMin;
						punkt->UstawPredkosc(szybkosc*Wektor(cos(predkoscKatPhi),sin(predkoscKatPhi),0));
						double masa=(double)rand()/(RAND_MAX+1)*(masaMax-masaMin)+masaMin;
						punkt->UstawMase(masa);				
						if(grupa==1) punkt->UstawKolor(1,1,0); else punkt->UstawKolor(0,1,0);

						//test nakladania sfer
						wynikTestuNakrywania=false;
						for(int j=0;j<i;++j)
						{
							PunktMaterialny* punktJ=PobierzPunktMaterialny(j);
							if((punkt->Polozenie()-punktJ->Polozenie()).Dlugosc()<=(punkt->Promien()+punktJ->Promien()))
								wynikTestuNakrywania=true;
						}
					}
					while(wynikTestuNakrywania);
				}						
			}
		}

	private:
		Wektor Sila(int indeks) const
		{
			return Wektor(0,0,0);
		}
};

class RuchyBrowna  : public ZderzajaceSiePunkty
{
	public:
		RuchyBrowna(int ilosc,int iloscKropliTluszczu,Wektor polozenieMin,Wektor polozenieMax,double predkoscMin,double predkoscMax,double masaMin,double masaMax)
			:ZderzajaceSiePunkty(ilosc+iloscKropliTluszczu,1,polozenieMin,polozenieMax,predkoscMin,predkoscMax,masaMin,masaMax)
		{	
			for(int i=0;i<ilosc;i++) PobierzPunktMaterialny(i)->UstawPromien(0.05);

			for(int i=0;i<iloscKropliTluszczu;i++)
			{
				//nie sprawdzam czy nie pokrywaja sie z innymi punktami i kroplami (zakladam, ze w rzeczywistosci moga)
				PunktMaterialny* ostatniPunkt=PobierzPunktMaterialny(ilosc+i);
				ostatniPunkt->UstawKolor(1,1,0,0.75);
				ostatniPunkt->UstawMase(10);			
				ostatniPunkt->UstawPromien(0.3);			
			}
		}
};


//Sterowanie - klawiatura
class Poduszkowce : public ZderzajaceSiePunkty
{
	private:
		PunktMaterialny* sterowanyPunkt;
		Wektor przyspieszeniePoduszkowca;
		double minX,maxX,minY,maxY;

	public:
		Poduszkowce(int ilosc,Wektor polozenieMin,Wektor polozenieMax,double predkoscMin,double predkoscMax,double masaMin,double masaMax)
		  :ZderzajaceSiePunkty(ilosc,1,polozenieMin,polozenieMax,predkoscMin,predkoscMax,masaMin,masaMax),
		  przyspieszeniePoduszkowca(Wektor::Zero())
		{
			sterowanyPunkt=PobierzPunktMaterialny(0);
			sterowanyPunkt->UstawKolor(0,1,0);
			sterowanyPunkt->UstawPolozenie(Wektor(polozenieMin.X,0,0));
			sterowanyPunkt->UstawPredkosc(Wektor(0,0,0));
		}

		Wektor PobierzPrzyspieszeniePoduszkowca()
		{
			return przyspieszeniePoduszkowca;
		}

		void UstawPrzyspieszeniePoduszkowca(Wektor przyspieszenie)
		{
			przyspieszeniePoduszkowca=przyspieszenie;			
		}

	private:
		Wektor SilaDzialajacaNaPoduszkowiec() const
		{
			return sterowanyPunkt->Masa()*przyspieszeniePoduszkowca;
		}

		Wektor Sila(int indeks) const
		{
			Wektor sila=ZderzajaceSiePunkty::Sila(indeks);
			if(indeks==0) sila+=SilaDzialajacaNaPoduszkowiec();
			return sila;
		}
};

#endif