/*!\file window.c
 * \brief GL4Dummies, exemple progressif d'éclairage
 * \author Farès BELHADJ, amsi@up8.edu
 * \date February 02 2025
 */

#include <GL4D/gl4du.h>
#include <GL4D/gl4duw_SDL2.h>
#include <GL4D/gl4dp.h>
#include <GL4D/gl4dg.h>
#include <SDL_image.h>
#include <vector>
#include "animations.h"
#include "audioHelper.h"
#include "point3d.hpp"
#include "mobile.hpp"
#include "bezier.hpp"


enum OCTANTS {
	OCT1 = 1,
	OCT2 = 2,
	OCT3 = 4,
	OCT4 = 8,
	OCTALL = OCT1 | OCT2 | OCT3 | OCT4
};


static void init(void);
static void update(double dt);
static void draw(void);
static void resize(int w, int h);
static void quit(void);
static void bresenham(int r, int rx, int y, int mouth);
static void bresenham3d(int r, int useMouth, int full);
static void drawCube(int r,int x,int y,int z,int mouth, int staticMouth);
static Point3d drawCam(double);
static void drawPacman(double dt);
static void drawGommes(double dt);
static void drawFantom(double dt);
static void drawWalls();

static int _ww = 1920, _wh = 1080;

static GLuint _pId = 0;
static GLuint _pTextId = 0;

static GLuint _cubeId = 0;
static GLuint _quadId = 0;

static GLuint _texId = 0;

#define VOXSIZE 0.25

// Besoin de transmettre le transform à notre voxel à part
static GLfloat _cubeScale[3] = {1.0,1.0,1.0};
static GLfloat _cubeTrans[3] = {0.0, 0.0, 0.0};
static GLfloat _cubeRot[4] = {0.0,0.0,0.0,0.0};

#define resetCubeTrans() {\
	_cubeTrans[0] = 0.0; _cubeTrans[1] = 0.0; _cubeTrans[2] = 0.0;\
}
#define resetCubeScale() {\
	_cubeScale[0] = 1.0; _cubeScale[1] = 1.0; _cubeScale[2] = 1.0;\
}
#define resetCubeRot() {\
	_cubeRot[0] = 0.0; _cubeRot[1] = 0.0; _cubeRot[2] = 0.0; _cubeRot[3] = 0.0;\
}
#define resetCubeAll() {\
		resetCubeTrans(); resetCubeScale(); resetCubeRot();\
}


// static variable for elapsed cause needed a lot (probably)
static double elapsed = 0.0;

// From this time, the cam will stop following pacman
#define STOP_FOLLOW_TIME 71.0f
static BAnim _camAnim = {{
    {{
			{ 22.0f,  0.0f, 7.0f},
			{ 22.0f,  0.0f, 15.0f},
			{-22.0f, 7.0f, 15.0f},
			{-22.0f, 7.0f, 7.0f},
		},10.0f
	},
	{{ {-22.0f, 7.0f, 7.0f} },21.0f},
	{{
			{-22.0f, 7.0f, 7.0f},
			{-22.0f, 7.0f, 15.0f},
			{ 22.0f, 7.0f, 15.0f},
			{ 22.0f, 7.0f, 7.0f}
		}, 5.0f
	},
	{{ { 22.0f, 7.0f, 7.0f} },14.0f},
	{{
			{ 22.0f, 7.0f, 7.0f},
			{ 22.0f, 7.0f, 15.0f},
			{-22.0f, 7.0f, 15.0f},
			{-22.0f, 7.0f, 7.0f}
		}, 1.0f
	},
	{{ {-22.0f, 7.0f, 7.0f} },2.0f},
	{{
			{-22.0f, 7.0f, 7.0f},
			{-22.0f,7.0f,-22.0f},
			{-22.0f,7.0f,-22.0f},
			{-7.0f, 7.0f, -22.0f}
		}, 0.5f},
	{{ {-7.0f, 7.0f, -22.0f}}, 8.5f},
	// Hesitation
	{{
			{-7.0f, 7.0f, -22.0f},
			{-15.0f, 7.0f, -22.0f},
			{-15.0f, 7.0f, 22.0f},
			{-7.0f, 7.0f, 22.0f}
		}, 3},
	{{ {-7.0, 7.0, 22.0}},1},
	{{

			{-7.0f, 7.0f, 22.0f},
			{-22.0f,7.0f, 22.0f},
			{-22.0f,7.0f, 22.0f},
			{-22.0f, 7.0f, 7.0f}
		}, 1.0},
	{{ {-22.0f, 7.0f, 7.0f}},0.5},
	{{
			{-22.0f, 7.0f, 7.0f},
			{-22.0f, 7.0f,-15.0f},
			{ 22.0f, 7.0f,-15.0f},
			{ 22.0f, 7.0f, 7.0f}
		}, 1.5},
	{{
			{ 22.0f, 7.0f, 7.0f},
			{ 22.0f, 7.0f,-15.0f},
			{-40.0f, 12.0f,-15.0f},
			{-40.0f, 12.0f, 0.0f}
		},2},
	{{ {-40.0f, 12.0f, 150.0f}},3.5}, // End
	{{
			{-40.0f, 12.0f, 150.0f},
			{-40.0, 2.0, 150.0},
			{22.5, 2.0, 150.0}
		},5},
	{{ {22.5, 2.0, 150.0} }, 5}
  }};
static BAnim _camLookAt = {{
	{{
			{0.0f, 0.0f, 0.0f},
			{0.0f, 0.0f, 0.0f},
			{0.0f, 0.0f, 0.0f},
			{30.0f, 3.0f, 0.0f}
		},10.0f 
	},
	{{ {30.0f, 3.0f, 0.0f} },21.0f},
	{{
			{30.0f, 3.0f, 0.0f},
			{-30.0f, 3.0f, 0.0f}
		}, 5.0f},
	{{ {-30.0f, 3.0f, 0.0f} }, 14.0f},
	{{
			{-30.0f, 3.0f, 0.0f},
			{-30.0f, 3.0f, -30.0f},
			{30.0f, 3.0f, -30.0f},
			{30.0f, 3.0f, 0.0f}
		}, 1.0f},
	{{ {30.0f, 3.0f, 0.0f} },2.0f},
	{{
			{30.0f, 3.0f, 0.0f},
			{30.0f, 3.0f, 30.0f},
			{30.0f, 3.0f, 30.0f},
			{0.0f, 3.0f, 30.0f}
		},0.5f},
	{{ {0.0f, 3.0f, 30.0f} },8.5f},
	// Hesitation
	{{
			{ 0.0f, 3.0f, 30.0f},
			{30.0f, 3.0f, 30.0f},
			{30.0f, 3.0f,-30.0f},
			{ 0.0f, 3.0f,-30.0f}
		}, 3},
	{{ { 0.0f, 3.0f,-30.0f}},1},
	{{
			{ 0.0f, 3.0f,-30.0f},
			{30.0f, 3.0f,-30.0f},
			{30.0f, 3.0f,-30.0f},
			{30.0f, 3.0f, 0.0f}
		}, 1.0},
	{{ { 30.0f, 3.0f, 0.0f} }, 0.5f},
	{{
			{30.0f, 3.0f, 0.0f},
			{30.0f, 3.0f, 30.0f},
			{-30.0f, 3.0f, 30.0f},
			{-30.0f, 3.0f, 0.0f}
		}, 1.5},
	{{
			{-30.0f, 3.0f, 0.0f},
			{-30.0f, 3.0f, 30.0f},
			{30.0f, 3.0f, 30.0f},
			{30.0f, 3.0f, 0.0f}
		},2},
	{{ {30.0f, 3.0f, 150.0f}},3.5}, // End
	{{
			{30.0f, 3.0f, 150.0f},
			{30.0f, 2.0f, 150.0f},
			{50.0f, 2.0f, 150.0f}
		}, 5},
	{{ {50.0f, 2.0f, 150.0f} },5}
	}};

#define PACMAN_SHAPE_TIME STOP_FOLLOW_TIME + 1.5
#define PARTICLE_START_TIME STOP_FOLLOW_TIME + 1.1
static BAnim _pacPosAnim = {{
	{{
			{0.0f, 0.0f, 0.0f},
			{300.0f, 0.0f, 0.0f}
		}, 17.0f},
	{{ { 300.0f,0,0}},9.0f},
	{{
			{ 300.0f,0,0},
			{ -50.0f, 0.0}
		}, 21.0f},
	{{ {-50.0f, 0,0}},4},
	{{
			{-50.0f, 0,0},
			{0.0, 0.0, 0.0}
		}, 2},
	{{ {0.0, 0.0, 0.0}},0.5},
	{{
			{0,0,0},
			{0,0,150}
		}, 7.5f},
	{{ {0,0,150.0}}, 10.0},
	{{
			{0,0,150},
			{-10,0,150}
		},1},
	{{
			{-10,0,150},
			{30, 0, 150}
		},0.5},
	{{{0,0,0}},100},
	}};
static BAnim _pacRotAnim = {{
	{{{0,0,0}},25},
	{{
			{0,0,0},
			{180,0,0}
		},1},
	{{{180,0,0}},24},
	{{
			{180,0,0},
			{0,0,0}
		},1},
	{{{0,0,0}},2},
	{{
			{0,0,0},
			{-90,0,0}
		},0.5},
	{{{-90,0,0}},8.5},
	// Hesitation
	{{
			{-90, 0, 0},
			{90, 0, 0}
		}, 3},
	{{ {90,0,0}},1},
	{{
			{90, 0, 0},
			{0,0,0}
		}, 1.0},
	{{ {0,0,0}},0.5},
	{{
			{0,0,0},
			{-180,0,0}
		}, 1.5},
	{{
			{-180,0,0},
			{0,0,0}
		},2},
	{{ {0,0,0}},100}, // End
	}};
static Point3d _pacPos = {0.0,0.0,0.0};
// Initialisé dans Init
static std::vector<Point3d> _pacGommes;

#define COL_INKY 1.0, 0.0, 0.0, 1.0
#define COL_PINKY 1.0, 0.72, 1.0, 1.0
#define COL_BLINKY 0.0, 1.0, 1.0, 1.0
#define COL_CLYDE 1.0, 0.72, 0.32, 1.0
static BAnim _inkyAnim   = {{
	{{{ 475.0, 0.0, -50.0}},14},
	{{
			{ 475, 0.0, -50.0},
			{ 475, 0.0, 0.0}
		}, 7.0},
	{{ { 475, 0.0, 0.0}}, 4},
	{{
			{475,0,0},
			{225,0,0}
		}, 11},
	{{
			{225, 0, 0},
			{10,0,0}
		}, 24.0},
	{{ {10, 0,0}}, 1},
	{{
			{10,0,0},
			{10,0,130}
		},13},
	{{ {10,0,130}},100},
	}};
static BAnim _inkyRotAnim = {{
	{{{ 90.0, 0,0}},21},
	{{
			{ 90.0, 0,0},
			{ 180, 0, 0}
		}, 4.0},
	{{ { 180, 0, 0}}, 35},
	{{
			{180, 0, 0},
			{90, 0, 0}
		},1.0f},
	{{ {90, 0, 0} }, 12},
	{{
			{90, 0, 0},
			{45, 0, 0}
		}, 1.0},
	{{ {45,0,0} }, 100},
	}};
static BAnim _pinkyAnim  = {{
	{{{ -225.0, 0.0, -50.0}},45.0},
	{{
			{-225.0, 0.0, -50.0f},
			{-225.0, 0.0, 0.0f}
		}, 5.0f},
	{{{ -225.0f, 0, 0}}, 3.0f},
	{{
			{-225.0f, 0, 0},
			{-10.0f, 0, 0}
		}, 7.0f},
	{{ {-10.0f, 0, 0}}, 1},	
	{{
			{-10,0,0},
			{-10,0,140}
		},13},
	{{ {-10,0,140}},100}
	}};
static BAnim _pinkyRotAnim = {{
	{{{ 90.0, 0,0}},50.0f},
	{{
			{ 90.0, 0,0},
			{ 0, 0, 0}
		}, 3.0},
	{{ { 0, 0, 0}}, 7},
	{{
			{0,0,0},
			{90,0,0}
		}, 1},
	{{ {90, 0, 0}},12},
	{{
			{90, 0, 0},
			{45, 0, 0}
		}, 1.0},
	{{ {45,0,0} }, 100},
	}};
static BAnim _blinkyAnim = {{
	{{{ 50.0, 0.0, 355.0}},52.0},
	{{
			{50,0,355.0},
			{10,0,355.0f}
		},4},
	{{ {10,0,355}},1},
	{{
			{10,0,355},
			{10,0,170}
		},17},
	{{ { 10,0,170}},100}
	}};
static BAnim _blinkyRotAnim = {{
	{{{180, 0,0}}, 56},
	{{
			{180, 0, 0},
			{270, 0, 0}
		},1},
	{{ {270, 0, 0}},16},
	{{
			{-90, 0, 0},
			{-45, 0, 0}
		},1},
	{{ {-45, 0, 0} },100}
	}};
static BAnim _clydeAnim  = {{
	{{{-50.0,  0.0, 355.0}},52.0},
	{{
			{-50,0,355.0},
			{-10,0,355.0f}
		},4},
	{{ {-10,0,355}},1},
	{{
			{-10,0,355},
			{-10,0,160}
		},17},
	{{ {-10, 0, 160}},100}
	}};
static BAnim _clydeRotAnim = {{
	{{{0, 0,0}}, 56},
	{{
			{0, 0, 0},
			{-90, 0, 0}
		},1},
	{{ {-90, 0, 0}},16},
	{{
			{-90, 0, 0},
			{-45, 0, 0}
		},1},
	{{ {-45, 0, 0} },100}
	}};
#define COL_WALLS 0.1, 0.1, 0.62, 1.0
static std::vector<Mobile> _particles;
static std::vector<Point3d> _points;

/*
int main(int argc, char ** argv) {
  if(!gl4duwCreateWindow(argc, argv, "GL4Dummies", 20, 20, _ww, _wh, GL4DW_RESIZABLE | GL4DW_SHOWN))
    return 1;
  init();
  atexit(quit);
  gl4duwResizeFunc(resize);
  gl4duwDisplayFunc(draw);
  gl4duwMainLoop();
  return 0;
}
*/

void mainScene(int state){
	switch(state){
	case GL4DH_INIT:
		init();
		return;
	case GL4DH_FREE:
		quit();
		return;
	case GL4DH_UPDATE_WITH_AUDIO:
		return;
	default:
		draw();
		return;
	}
}

void init(void) {
  _pId = gl4duCreateProgram("<vs>shaders/light.vs", "<fs>shaders/light.fs", NULL);
  _pTextId = gl4duCreateProgram("<vs>shaders/credits.vs", "<fs>shaders/credits.fs", NULL);
  _cubeId = gl4dgGenCubef();
  _quadId = gl4dgGenQuadf();
  SDL_GL_SetSwapInterval(1);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  gl4duGenMatrix(GL_FLOAT, "proj");
  gl4duGenMatrix(GL_FLOAT, "mod");
  gl4duGenMatrix(GL_FLOAT, "view");
  resize(_ww, _wh);


  // Init Pac-gommes
  // Main Corridor front
  for(int i = 0; i < 42 ; ++i){
	  _pacGommes.push_back({i*10.0f + 5.0f, 0, 0});
  }
  // far corridor inky
  for(int i = 0; i < 8 ; ++i){
	  _pacGommes.push_back({475.0f, 0, i*10.0f - 40.0f});
  }
  // Main Corridor back
  for(int i = 0; i < 18 ; ++i){
	  _pacGommes.push_back({-i*10.0f - 35.0f , 0, 0});
  }
  // far Corridor pinky
  for(int i = 0; i < 8; ++i){
	  _pacGommes.push_back({-225.0f, 0, i*10.0f - 40.0f});
  }
  // Side Corridor
  for(int i = 0; i < 24 ; ++i){
	  _pacGommes.push_back({0.0f, 0.0f, i*10.0f + 50.0f});
  }
  // far side corridor
  for(int i = 0; i < 8 ; ++i){
	  _pacGommes.push_back({i*10.0f - 40.0f, 0, 355.0f});
  }

  glGenTextures(1, &_texId);
  glBindTexture(GL_TEXTURE_2D, _texId);
  
  SDL_Surface *s = IMG_Load("images/10.png");
  assert(s);
  SDL_Surface* d = SDL_CreateRGBSurface(0, s->w, s->h, 32, R_MASK, G_MASK, B_MASK, A_MASK);
  SDL_BlitSurface(s,NULL,d,NULL);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, d->w, d->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, d->pixels);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  SDL_FreeSurface(s);
  SDL_FreeSurface(d);
}

void update(double dt){
	// Update position des pacman
	_pacPos = _pacPosAnim.evalAnim(elapsed);

	// Vérif mangeage des gommes
	auto it = _pacGommes.begin() ;
	while(it != _pacGommes.end()){
		if((_pacPos - *it).normal2() < 0.25f){
			_points.push_back(*it);
			it = _pacGommes.erase(it);
			break;
		} else
			++it;
	}
	it = _points.begin();
	while(it != _points.end()){
		if(it->y > 30.0f)
			it = _points.erase(it);
		else{
			it->y += 10.0f * dt;
			++it;
		}
	}
	static bool initedParticles = false;
	if(elapsed > PARTICLE_START_TIME){
		if(!initedParticles){
			mobileInit(_particles, {25, 0, 150});
			initedParticles = true;
		}
		mobileUpdate(_particles, dt);
	}
	return;
}


void drawCube(int r,int x, int y, int z, int mouth, int staticMouth = 0.0){
	if(x == 0) return;
	const double pi4 = M_PI / 5.5;
	float sinEl;
	if(staticMouth)
		sinEl = 1.0;
	else
		sinEl = sin(elapsed * 3);
	
	if(sinEl < 0) sinEl *= -1;
	//int eX = abs(cos(pi4*sinEl) * x);
	int eY = abs(sin(pi4*sinEl) * r);
	int modX = 0;
	gl4duLoadIdentityf();

	// ON ajoute VOXSIZE à _cubeScale
	// Nécessaire pour que les scales du voxel soient correctes
	for(int i = 0 ; i < 3 ; ++i){
		_cubeScale[i] *= VOXSIZE;
	}
	
	// On dessine la sphere normalement (pas la bouche)
	if(abs(y) >= abs(ceil(eY))){
		mouth = 0;
	} 
	
	if(mouth){
		// Conversion en float pour s'assurer que par d'erreur d'int
		modX = x * (abs(y)/(float) eY);
		if(modX < 1)
			modX = 1;
		modX = (modX + x)/2;

		gl4duTranslatef(_cubeTrans[0],
						_cubeTrans[1],
						_cubeTrans[2]);
		// Rotate needs to happen after local translate
		gl4duRotatef(_cubeRot[0],_cubeRot[1],_cubeRot[2],_cubeRot[3]);
		gl4duTranslatef(-(x-modX) * _cubeScale[0],
						y * _cubeScale[1],
						z * _cubeScale[2]);
		gl4duScalef(modX * _cubeScale[0],
					_cubeScale[1],
					_cubeScale[2]);

	} else {		
		gl4duTranslatef(_cubeTrans[0],
						_cubeTrans[1],
						_cubeTrans[2]);
		gl4duRotatef(_cubeRot[0],_cubeRot[1],_cubeRot[2],_cubeRot[3]);
		gl4duTranslatef(_cubeScale[0],
						y * _cubeScale[1],
						z * _cubeScale[2]);		
		gl4duScalef( x * _cubeScale[0],
					 _cubeScale[1],
					 _cubeScale[2]);

	}
	
	gl4duSendMatrices();
	gl4dgDraw(_cubeId);

	// On enlève VOXSIZE de _cubeScale
	for(int i = 0 ; i<3 ; ++i) {
		_cubeScale[i] /= VOXSIZE;
	}
}

// R le rayon de la sphere, rx le rayon du cercle qu'on dessine
void bresenham(int r, int rx, int y, int mouth){
	int maxx = ceil(rx/sqrt(2.0f))+1, r2 = rx*rx;
	for(int x = 0, z; x < maxx ; x += 2){
		z = ceil(sqrtf(r2-x*x));
		drawCube(r, x, y, z, mouth);
		drawCube(r, x, y,-z, mouth);
		drawCube(r, z, y, x, mouth);
	 	drawCube(r, z, y,-x, mouth);
	}	
}

void bresenham3d(int r, int useMouth, int octan){
	int maxx = ceil(r/sqrt(2.0f))+1, r2 = r*r;
	for(int x = 0, y; x < maxx ; x += 2){
		y = ceil(sqrtf(r2 - x*x));
		if(OCT1 & octan)
			bresenham(r,x, y,0);
		if(OCT2 & octan)
			bresenham(r,y, x,useMouth);
		if(OCT3 & octan)
			bresenham(r,y,-x,useMouth);
		if(OCT4 & octan)
			bresenham(r,x,-y,0);
	}
}


void drawPacman(double){
    _cubeRot[0] = _pacRotAnim.evalAnim(elapsed).x; _cubeRot[2] = 1.0;
	glUniform4f(glGetUniformLocation(_pId, "col"), 0.80f, 0.80f, 0.0f, 1.0f);
	_cubeTrans[0] = _pacPos.x; _cubeTrans[2] = _pacPos.z;
	bresenham3d(20, 1, OCTALL);
	resetCubeTrans();
	resetCubeRot();
}

void drawGommes(double){
	glUniform4f(glGetUniformLocation(_pId, "col"), 1.0f, 1.0f, 1.0f, 1.0f);
	_cubeScale[0] = _cubeScale[1] = _cubeScale[2] = 0.6;
	for(auto it = _pacGommes.begin(); it != _pacGommes.end() ; ++it){
		_cubeTrans[0] = it->x;
		_cubeTrans[2] = it->z;
		bresenham3d(5, 0, OCTALL);
	}
	resetCubeScale();
	resetCubeTrans();
}

/* Fantoms work even though matematically this is disgusting */
void drawFantom(double){
	const float fantRad = 20.0;
	GLfloat baseTrans[3] = {_cubeTrans[0],_cubeTrans[1],_cubeTrans[2]};
	// sin(PI/4) * rayon pour hauteur de la transition d'octan
	int octY = ceil(0.7071067 * fantRad);
	// Chapeau
	_cubeTrans[1] = baseTrans[1] + 2.0;
	bresenham3d(fantRad,0,OCT1 | OCT2);
	// Corps + vagues	
	// On refait un brenseham mais en modulant la hauteur en fonction 
	int maxx = ceil(fantRad/sqrt(2.0f))+1, r2 = fantRad*fantRad;
	int yRest = fantRad - octY, yMod;
	for(int x = 0, z; x < maxx ; x += 2){
		z = ceil(sqrtf(r2-x*x));
		// Octants 1 et 2
		yMod = sinf(z /2 + elapsed * 2) * yRest * 2/3;
		_cubeScale[1] = octY + yMod/2 + yRest/6;
		_cubeTrans[1] = baseTrans[1] -((yMod/2 + yRest/6)*VOXSIZE);
		drawCube(maxx-1, x, 0, z, 0);
		drawCube(maxx-1, x, 0,-z, 0);
		// Octants 3 et 4
		yMod = sinf(x/2 + elapsed * 2) * yRest * 2/3;
		_cubeScale[1] = octY + yMod/2 + yRest/6;
		_cubeTrans[1] = baseTrans[1] -((yMod/2 + yRest/6) * VOXSIZE);
		drawCube(maxx-1, z, 0, x, 0);
	 	drawCube(maxx-1, z, 0,-x, 0);
	}
	
	// Eyes
	#define PI8 0.392699082
	glUniform4f(glGetUniformLocation(_pId, "col"), 1.0f, 1.0f, 1.0f, 1.0f);
	_cubeScale[0] = 0.7;_cubeScale[1] = 0.7;_cubeScale[2] = 0.7;
	_cubeTrans[1] = baseTrans[1] + (octY * 2/3) * VOXSIZE;
	_cubeTrans[0] = baseTrans[0] +
		fantRad * VOXSIZE * cos(_cubeRot[0]/180 * M_PI - PI8);
	_cubeTrans[2] = baseTrans[2] +
		fantRad * VOXSIZE * sin(_cubeRot[0]/180 * M_PI - PI8);
	bresenham3d(7, 0, OCTALL);
	_cubeTrans[0] = baseTrans[0] +
		fantRad * VOXSIZE * cos(_cubeRot[0]/180 * M_PI + PI8);
	_cubeTrans[2] = baseTrans[2] +
		fantRad * VOXSIZE * sin(_cubeRot[0]/180 * M_PI + PI8);
	bresenham3d(7, 0, OCTALL);

	// Pupilles
	#undef PI8
	// Corrected to center pupil in the eye (equals PI/8 * 3/4)
	#define PI8 0.29452431127
	glUniform4f(glGetUniformLocation(_pId, "col"), 0.0f, 0.0f, 0.0f, 1.0f);
	_cubeTrans[0] = baseTrans[0] +
		(fantRad + 4.5) * VOXSIZE * cos(_cubeRot[0]/180 * M_PI - PI8);
	_cubeTrans[2] = baseTrans[2] +
		(fantRad + 4.5) * VOXSIZE * sin(_cubeRot[0]/180 * M_PI - PI8);
	drawCube(2,1,0,0,0);
	_cubeTrans[0] = baseTrans[0] +
		(fantRad + 4.5) * VOXSIZE * cos(_cubeRot[0]/180 * M_PI + PI8);
	_cubeTrans[2] = baseTrans[2] +
		(fantRad + 4.5) * VOXSIZE * sin(_cubeRot[0]/180 * M_PI + PI8);
	drawCube(2,1,0,0,0);
	resetCubeTrans();
	resetCubeScale();
	#undef PI8
}

void drawWalls(){
  // Draw Sol
  glUniform4f(glGetUniformLocation(_pId, "col"), COL_WALLS);
  gl4duTranslatef(0.0,-10.0f,0.0);
  gl4duScalef(10000,1,10000);
  gl4duRotatef(-90,1.0,0.0,0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);


  // Draw Murs
  gl4duLoadIdentityf();
  gl4duTranslatef(125.0, 10.0, -30.0);
  gl4duScalef(325,20,1);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  // Two walls on the right for the other corridor
  gl4duLoadIdentityf();
  gl4duTranslatef(237.5, 10.0, 30.0);
  gl4duScalef(212.5, 20, 1);
  gl4duRotatef(180, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  gl4duLoadIdentityf();
  gl4duTranslatef(-112.5, 10.0, 30.0);
  gl4duScalef(87.5, 20, 1);
  gl4duRotatef(180, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  // End of main corridor walls
  gl4duLoadIdentityf();
  gl4duTranslatef(500.0, 10.0, 0.0);
  gl4duScalef(1,20,450);
  gl4duRotatef(-90, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  gl4duLoadIdentityf();
  gl4duTranslatef(-250.0, 10.0, 0.0);
  gl4duScalef(1,20,450);
  gl4duRotatef(90, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  // Walls in the corridor
  gl4duLoadIdentityf();
  gl4duTranslatef(-25.0, 10.0, 180.0);
  gl4duScalef(1,20,150);
  gl4duRotatef(90, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  gl4duLoadIdentityf();
  gl4duTranslatef(25.0, 10.0, 180.0);
  gl4duScalef(1,20,150);
  gl4duRotatef(-90, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);

  // End of side corridor wall
  gl4duLoadIdentityf();
  gl4duTranslatef(0, 10.0, 380.0);
  gl4duScalef(400.0, 20, 1);
  gl4duRotatef(180, 0.0, 1.0, 0.0);
  gl4duSendMatrices();
  gl4dgDraw(_quadId);
	  
  // Pacman shape
  if(elapsed > PACMAN_SHAPE_TIME){
	  _cubeTrans[0] = 25.0;
	  _cubeTrans[2] = 150.0;
	  _cubeRot[0] = -90; _cubeRot[2] = 1.0;
	  _cubeScale[0] = _cubeScale[1] = _cubeScale[2] = 1.1;
	  glUniform4f(glGetUniformLocation(_pId, "col"), 0,0,0,1);
	  int maxx = ceil(20/sqrt(2.0f))+1, r2 = 20*20;
	  for(int x = 0, y; x < maxx ; x += 2){
		  y = ceil(sqrtf(r2 - x*x));
		  drawCube(20,x, y,0,0,1);
		  drawCube(20,y, x,0,1,1);
		  drawCube(20,y,-x,0,1,1);
		  drawCube(20,x,-y,0,0,1);
	  }
	  resetCubeAll();
  }
}

Point3d drawCam(double){
  Point3d camPos = _camAnim.evalAnim(elapsed);
  Point3d camLookAt = _camLookAt.evalAnim(elapsed);
  gl4duBindMatrix("view");
  gl4duLoadIdentityf();
  if(elapsed < STOP_FOLLOW_TIME){
	  camPos = _pacPos + camPos;
	  camLookAt = _pacPos + camLookAt;
  }
  gl4duLookAtf(camPos.x,
			   camPos.y,
			   camPos.z,
			   camLookAt.x,
			   camLookAt.y,
			   camLookAt.z,
			   0.0f, 1.0f, 0.0f);
  
  glUniform3f(glGetUniformLocation(_pId, "Lp"),
			  camPos.x,
			  camPos.y,
			  camPos.z);
  return camPos;
}


void draw(void) {
  /* Bloc Temps */
  static double pt = -1.0;
  double tmpt, dt;
  if(pt < 0.0)
	  pt = gl4dGetElapsedTime();
  dt = ((tmpt = gl4dGetElapsedTime()) - pt) / 1000.0;
  elapsed += dt;
  pt = gl4dGetElapsedTime();
  /* Bloc Temps */
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
  glUniform1f(glGetUniformLocation(_pId, "temps"), elapsed / 1000.0f);

  update(dt);
  
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glUseProgram(_pId);

  // On récup la position de la cam en la dessinant pour dessiner le score
  Point3d camPos = drawCam(dt);

  gl4duBindMatrix("mod");
  gl4duLoadIdentityf();

  // Draw Walls & Sol
  drawWalls();

  if(elapsed > PARTICLE_START_TIME){
	  glUniform4f(glGetUniformLocation(_pId, "col"), COL_WALLS);
	  drawMobiles(_particles, _cubeId);
  }
  
  // Draw Pacman
  drawPacman(dt);

  // Draw PacGomme
  drawGommes(dt);

  // Draw Fantom
  // Color of fantom needs to be defined before call
  _cubeRot[0] = _inkyRotAnim.evalAnim(elapsed).x; _cubeRot[2] = 1.0;
  Point3d ghost = _inkyAnim.evalAnim(elapsed);
  _cubeTrans[0] = ghost.x; _cubeTrans[2] = ghost.z;
  glUniform4f(glGetUniformLocation(_pId, "col"), COL_INKY);
  drawFantom(dt);

  _cubeRot[0] = _pinkyRotAnim.evalAnim(elapsed).x;
  ghost = _pinkyAnim.evalAnim(elapsed);
  _cubeTrans[0] = ghost.x; _cubeTrans[2] = ghost.z;
  glUniform4f(glGetUniformLocation(_pId, "col"), COL_PINKY);
  drawFantom(dt);

  _cubeRot[0] = _blinkyRotAnim.evalAnim(elapsed).x;
  ghost = _blinkyAnim.evalAnim(elapsed);
  _cubeTrans[0] = ghost.x; _cubeTrans[2] = ghost.z;
  glUniform4f(glGetUniformLocation(_pId, "col"), COL_BLINKY);
  drawFantom(dt);

  _cubeRot[0] = _clydeRotAnim.evalAnim(elapsed).x;
  ghost = _clydeAnim.evalAnim(elapsed);
  _cubeTrans[0] = ghost.x; _cubeTrans[2] = ghost.z;
  glUniform4f(glGetUniformLocation(_pId, "col"), COL_CLYDE);
  drawFantom(dt);

  resetCubeRot();

  // Dessin des scores en dernier pour qu'ils soient toujours devant le reste
  
  glUseProgram(_pTextId);

  //glDisable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, _texId);
  glUniform1i(glGetUniformLocation(_pTextId, "tex"), 0);
  glUniform1i(glGetUniformLocation(_pTextId, "inv"), 1);
  glUniform1f(glGetUniformLocation(_pTextId, "transp"), 1.0);
  glUniform1f(glGetUniformLocation(_pTextId, "tresh"), 1.0);
  glUniform1f(glGetUniformLocation(_pTextId, "pixMult"), 1.0);
  gl4duBindMatrix("mod");
  for(auto it = _points.begin();it != _points.end() ; ++it){
	  gl4duLoadIdentityf();
	  gl4duTranslatef(it->x,
					  it->y,
					  it->z);

	  // Source of math for angle : https://stackoverflow.com/a/21484228
	  gl4duRotatef(360 - (atan2(camPos.z - _pacPos.z,camPos.x - _pacPos.x) - atan2(1,0)) *
				   57.2958, // rad to deg constant
				   0,1,0);
	  gl4duScalef(0.6, 0.6, 1.0);
	  gl4duSendMatrices();
	  gl4dgDraw(_quadId);
  }
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_BLEND);
  
  glUseProgram(0);
}

void resize(int w, int h) {
  GLfloat ratio = h / (GLfloat)w;
  _ww = w; _wh = h;
  gl4duBindMatrix("proj");
  gl4duLoadIdentityf();
  gl4duFrustumf(-1.0f, 1.0f, -1.0f * ratio, 1.0f * ratio, 2.0f, 1100.0f);
  glViewport(0, 0, _ww, _wh);
}

void quit(void) {
	if(_texId){
		glDeleteTextures(1, &_texId);
	}
	return;
}

