//Scene.cpp

#include "stdafx.h"
#include "Scene.h"
#include "Vector.h"

CCamera::CCamera()
{
	Forward = _X;
	Right	= _Y;
	Up		= _Z;

	MinFlat		= 0.01f;
	MaxFlat		= 1e6f;
	ViewAngle	= 45.0f;
}

void CCamera::SetCamera(CVector3 &Pos0, CVector3 &LookAt)
{
	Pos		= Pos0;
	Forward = !(LookAt-Pos);

	//if(fabs(cos(Forward, _V(0, 0, 1))) < 0.9f)
	//{
		Right	= !(CVector3(0, 0, 1)&Forward);
		Up		= !(Forward&Right);
	//}
	/*else
	{
		Up		= !(Forward&CVector3(0, 1, 0));
		Right	= !(Up&Forward);
	}*/

	FlatCenter = Pos + Forward*MinFlat;
	PixelWidth = float(MinFlat*tan(ViewAngle*M_PI/180)*2/Width);
	PixelHeight = PixelWidth;
	//PixelHeight = MinFlat*tan(ViewAngle*M_PI/180)*2/Height;//PixelWidth*Height/Width;
}

CMaterial::CMaterial()
{
	Exponent = 0;
}

void CMaterial::CreateStandard()
{
	CreateColored(_1);
}

void CMaterial::CreateColored(CVector3 &Color, CVector3 &Reflectivity0)
{
	Ambient = _L(0.3f) | Color;
	Diffuse = _L(0.3f) | Color;
	Specular = _L(0.2f) | Color;
	Reflectivity = Reflectivity0;
	Exponent = 30;
}

CFlat::CFlat()
{
}

void CFlat::CheckCollision(CRay &Ray, CRay &NewRay, CVector3 &Normal)
{
	NewRay = CRay();
	if(!Ray.Direction.Z || (Ray.Start.Z == Pos.Z) ||
		((Ray.Direction.Z < 0) && (Ray.Start.Z < Pos.Z)) || ((Ray.Direction.Z > 0) && (Ray.Start.Z > Pos.Z))) // flat Z = 0 and ray don't cross
		return;

	float t = (Pos.Z - Ray.Start.Z)/Ray.Direction.Z;
	float X = Ray.Start.X + t*Ray.Direction.X;
	float Y = Ray.Start.Y + t*Ray.Direction.Y;

	NewRay.Start = _V(X, Y, Pos.Z);
	if(Ray.Direction.Z > 0)
		Normal = CVector3(0, 0, -1);
	else
		Normal = CVector3(0, 0, 1);
	NewRay.Direction = !(Mirror(Ray.Direction, Normal));
}

CSphere::CSphere()
{
	Radius = 2.0f;
}

void CSphere::CheckCollision(CRay &Ray, CRay &NewRay, CVector3 &Normal)
{
	Ray.Start -= Pos;
	
	NewRay = CRay();
	Normal = _0;

	/*if(Ray.Start.LengthSqr() < sqr(Radius))
		return;*/

	float DS = -Ray.Direction*Ray.Start; // cos (OC, HC)
	if(DS < 0)
		return;

	CVector3 H = Ray.Start + Ray.Direction*DS; // H = C + CH; CH = OC * cos(OC, CH)
	float x2 = sqr(Radius) - H.LengthSqr();
	if(x2 > 0)
	{
		NewRay.Start = H - sqrt(x2)*Ray.Direction;
		Normal = !NewRay.Start;
		NewRay.Start += Pos;
		NewRay.Direction = !(Mirror(Ray.Direction, Normal));
	}

	Ray.Start += Pos;
}

CLight::CLight()
{
	Color = _1;
	ConstAttenuation = 1.0f;
	LinearAttenuation = 0;
	QuadricAttenuation = 0;
}

CScene::CScene()
{
	Init();
}

CScene::~CScene()
{
	Destroy();
}

void CScene::Init()
{
	Spheres = NULL;
	Flats = NULL;
	Lights = NULL;

	bAmbient = true;
	bDiffuse = true;
	bSpecular = true;
}

void CScene::Create(int Width, int Height, int nFlats0, int nSpheres0, int nLights0)
{
	Camera.Width = Width;
	Camera.Height = Height;

	int i;
	nFlats = nFlats0;
	Flats = new CFlat[nFlats];
	for(i = 0; i < nFlats; i++)
		Flats[i].Material.CreateStandard();

	nSpheres = nSpheres0;
	Spheres = new CSphere[nSpheres];
	for(i = 0; i < nSpheres; i++)
		Spheres[i].Material.CreateStandard();

	nLights = nLights0;
	Lights = new CLight[nLights];
}

void CScene::Destroy()
{
	delete[] Spheres;
	delete[] Flats;
	delete[] Lights;
	Init();
}

void CScene::CheckCollision(CRay &Ray, CRay &NewRay, CVector3 &Normal, CMaterial &Material, float MinDist, float MaxDist)
{
	int i, j;
	NewRay = CRay();
	Material = CMaterial();
	CRay TestRay;
	CVector3 TempNormal;
	float Length = 0, TestLength, CameraDist;
	for(i = 0; i < nFlats + nSpheres; i++)
	{
		if(i < nFlats)
		{
			j = i;
			Flats[j].CheckCollision(Ray, TestRay, TempNormal);
		}
		else
		{
			j = i - nFlats;
			Spheres[j].CheckCollision(Ray, TestRay, TempNormal);
		}
		if(TestRay.Direction == _0)
			continue;

		CameraDist = ~(Camera.Pos - TestRay.Start);
		TestLength = ~(TestRay.Start - Ray.Start);
		if((!TestLength) || (Length && (TestLength > Length)) ||
			(TestLength <= MinDist) || (TestLength >= MaxDist) || (CameraDist <= Camera.MinFlat) || (CameraDist >= Camera.MaxFlat))
			continue;

		Length = TestLength;
		NewRay = TestRay;
		Normal = TempNormal;
		if(i < nFlats)
			Material = Flats[j].Material;
		else
			Material = Spheres[j].Material;
	}
}

CVector3 CScene::GetRayColor(CRay & Ray, int RecLevel)
{
	CRay NewRay;
	CRay ShadowRay, TempRay;
	CVector3 Normal, TempNormal;
	CMaterial Material, TempMaterial;
	int l;
	float Length, Radiance, LN, RV;
	CVector3 LightDir, R, V;
	CVector3 PixelColor, LightColor;

	CheckCollision(Ray, NewRay, Normal, Material);

	if(NewRay.Direction == _0)
		return _0;

	CVector3 ColorCoef = _1 - Material.Reflectivity;

	if(bAmbient)
		PixelColor = Material.Ambient;

	for(l = 0; l < nLights; l++)
	{
		V = !(Camera.Pos - NewRay.Start);
		LightDir = Lights[l].Pos - NewRay.Start;
		ShadowRay = CRay(NewRay.Start, LightDir);
		Length = ~LightDir;
		CheckCollision(ShadowRay, TempRay, TempNormal, TempMaterial, 0, Length);
		Radiance = (1.0f/(Lights[l].ConstAttenuation + Length*Lights[l].LinearAttenuation + Length*Length*Lights[l].QuadricAttenuation));
		if(TempRay.Direction == _0)
		{
			LN = cos(LightDir, Normal);
			if(bDiffuse && (LN > 0))
				LightColor += Material.Diffuse * LN;
			R = -!(Mirror(LightDir, Normal));
			RV = R*V;
			if(bSpecular && (RV > 0))
				LightColor += Material.Specular * pow(RV, Material.Exponent);
			LightColor |= (Lights[l].Color * Radiance);
			PixelColor += LightColor;
		}
	}

	if((RecLevel < 3) && (NewRay.Direction != _0) && (Material.Reflectivity != _0))
	{
		PixelColor |= ColorCoef;
		PixelColor += GetRayColor(NewRay, RecLevel+1) | Material.Reflectivity;
	}
	return PixelColor;
}

CVector3 CScene::CountPixel(int i, int j)
{
	i -= Camera.Width/2;
	j -= Camera.Height/2;
	CRay Ray(Camera.Pos, Camera.FlatCenter + i*Camera.PixelWidth*Camera.Right - j*Camera.PixelHeight*Camera.Up);
	
	return GetRayColor(Ray, 1);
}

