/*  TA3D, a remake of Total Annihilation
    Copyright (C) 2005  Roland BROCHARD

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA*/

/*-----------------------------------------------------------------------------------\
|                                         3do.h                                      |
|  ce fichier contient les structures, classes et fonctions nécessaires à la lecture |
| des fichiers 3do de total annihilation qui sont les fichiers contenant les modèles |
| 3d des objets du jeu.                                                              |
|                                                                                    |
\-----------------------------------------------------------------------------------*/

#ifndef __CLASSE_3DO
#define __CLASSE_3DO

#ifndef __GAF_CLASSES
#include "gaf.h"
#endif

		//	Classe pour la gestion des textures du jeu
class TEXTURE_MANAGER
{
public:
	int		nbtex;			// Nombre de textures
	GLuint	*gltex;			// Numéros de textures OpenGl
	char	**name;			// Noms des textures stockées
	BITMAP	**bmptex;		// Textures sous forme de bitmaps

	void init()
	{
		nbtex=0;
		gltex=NULL;
		name=NULL;
		bmptex=NULL;
	}

	void destroy()
	{
		if(bmptex) {
			for(int i=0;i<nbtex;i++)
				destroy_bitmap(bmptex[i]);
			free(bmptex);
			}
		if(gltex) {
			for(int i=0;i<nbtex;i++)
				glDeleteTextures(1,&(gltex[i]));
			free(gltex);
			}
		if(name) {
			for(int i=0;i<nbtex;i++)
				if(name[i])
					free(name[i]);
			free(name);
			}
		init();
	}

	TEXTURE_MANAGER()
	{
		init();
	}

	~TEXTURE_MANAGER()
	{
		destroy();
	}

	int get_texture_index(char *texture_name)
	{
		if(nbtex==0) return -1;
		for(int i=0;i<nbtex;i++)
			if(strcasecmp(texture_name,name[i])==0)
				return i;
		return -1;
	}

	GLuint get_gl_texture(char *texture_name)
	{
		int index=get_texture_index(texture_name);
		if(index==-1)
			return 0;
		return gltex[index];
	}

	BITMAP *get_bmp_texture(char *texture_name)
	{
		int index=get_texture_index(texture_name);
		if(index==-1)
			return NULL;
		return bmptex[index];
	}

	int load_gaf(byte *data);

	int all_texture_in_hpi(char *filename)
	{
		HPI_FILE hpi;
		hpi.load(filename);
		char *file;
		int n=0;
		while(file=hpi.find("/textures",n++)) {
			byte *data=hpi.extract_memory_file(file);
			load_gaf(data);
			free(data);
			}
		hpi.destroy();
		return 0;
	}

	int all_texture()
	{
		int nb=0;
		char **file=file_list("/textures",".gaf",&nb);
		for(int n=0;n<nb;n++) {
			byte *data=load_file(file[n]);
			load_gaf(data);
			free(data);
			}
		return 0;
	}
};

extern TEXTURE_MANAGER	texture_manager;

/*--------------Classes pour l'animation---------------------------------------------*/

#define FLAG_HIDE			0x1
#define FLAG_WAIT_FOR_TURN	0x2
#define FLAG_NEED_COMPUTE	0x4

struct AXE
{
	float	move_speed;
	float	move_distance;
	float	rot_angle;
	float	rot_speed;
	float	rot_accel;
	float	angle;
	float	pos;
	bool	rot_limit;
	bool	rot_speed_limit;
	float	rot_target_speed;
};

class SCRIPT_DATA
{
public:
	int	nb_piece;
	AXE	*axe[3];			// 3 axes (dans l'ordre x,y,z)
	short *flag;
	VECTOR *pos;
	VECTOR *dir;			// Orientation des objets (quand il n'y a qu'une ligne)

	inline void init()
	{
		nb_piece=0;
		axe[0]=axe[1]=axe[2]=NULL;
		flag=NULL;
		pos=NULL;
		dir=NULL;
	}

	inline SCRIPT_DATA()
	{
		init();
	}

	inline void destroy()
	{
		for(int i=0;i<3;i++)
			if(axe[i])
				free(axe[i]);
		if(dir)
			free(dir);
		if(pos)
			free(pos);
		if(flag)
			free(flag);
		init();
	}

	inline ~SCRIPT_DATA()
	{
		destroy();
	}

	inline void load(int nb)
	{
		destroy();		// Au cas où
		nb_piece=nb;
		flag=(short*) malloc(sizeof(short)*nb_piece);
		pos=(VECTOR*) malloc(sizeof(VECTOR)*nb_piece);
		dir=(VECTOR*) malloc(sizeof(VECTOR)*nb_piece);
		int i;
		for(i=0;i<nb_piece;i++) {
			flag[i]=0;
			pos[i].x=pos[i].y=pos[i].z=0.0f;
			dir[i]=pos[i];
			}
		for(i=0;i<3;i++) {
			axe[i]=(AXE*) malloc(sizeof(AXE)*nb_piece);
			for(int e=0;e<nb_piece;e++) {
				axe[i][e].move_speed=0.0f;
				axe[i][e].move_distance=0.0f;
				axe[i][e].pos=0.0f;
				axe[i][e].rot_angle=0.0f;
				axe[i][e].rot_speed=0.0f;
				axe[i][e].rot_accel=0.0f;
				axe[i][e].angle=0.0f;
				axe[i][e].rot_limit=true;
				axe[i][e].rot_speed_limit=false;
				axe[i][e].rot_target_speed=0.0f;
				}
			}
	}

	void move(float dt);
};

/*-----------------------------------------------------------------------------------*/

struct tagObject				// Structure pour l'en-tête du fichier
{
	int		VersionSignature;
	int		NumberOfVertexes;
	int		NumberOfPrimitives;
	int		OffsetToselectionPrimitive;
	int		XFromParent;
	int		YFromParent;
	int		ZFromParent;
	int		OffsetToObjectName;
	int		Always_0;
	int		OffsetToVertexArray;
	int		OffsetToPrimitiveArray;
	int		OffsetToSiblingObject;
	int		OffsetToChildObject;
};

struct tagPrimitive
{
	int		ColorIndex;
	int		NumberOfVertexIndexes;
	int		Always_0;
	int		OffsetToVertexIndexArray;
	int		OffsetToTextureName;
	int		Unknown_1;
	int		Unknown_2;
	int		Unknown_3;
};

struct tagVertex		// Structure pour lire les coordonnées des points
{
	int	x;
	int	y;
	int	z;
};

class OBJECT					// Classe pour la gestion des (sous-)objets des modèles 3do
{
public:
	int		nb_vtx;				// Nombre de points
	int		nb_prim;			// Nombre de primitives
	char	*name;				// Nom de l'objet
	OBJECT	*next;				// Objet suivant
	OBJECT	*child;				// Objet fils
	POINTF	*points;			// Points composant l'objet
	int		nb_p_index;			// Nombre d'indices de points
	int		nb_l_index;			// Nombre d'indices de lignes
	int		nb_t_index;			// Nombre d'indices de triangles
	VECTOR	pos_from_parent;	// Position par rapport à l'objet parent
	GLuint	*p_index;			// Tableau d'indices pour les points isolés
	GLuint	*l_index;			// Tableau d'indices pour les lignes
	GLuint	*t_index;			// Tableau d'indices pour les triangles
	short	*nb_index;			// Nombre d'indices par primitive
	VECTOR	*N;					// Tableau de normales pour les triangles
	GLuint	*tex;				// Tableau de numéros de texture OpenGl
	byte	*usetex;			// Tableau indiquant si une texture doit être appliquée
	int		selprim;			// Polygone de selection
	GLuint	gltex;				// Texture pour le dessin de l'objet
	bool	dtex;				// Indique si une texture objet doit être détruite avec l'objet
	float	*tcoord;			// Tableau de coordonnées de texture
	GLuint	sel[4];				// Primitive de sélection
	int		script_index;		// Indice donné par le script associé à l'unité

private:

	GLuint	*shadow_index;		// Pour la géométrie du volume d'ombre
	int		*t_line;					// Repère les arêtes
	int		*line_v_idx[2];
	int		nb_line;

private:
	inline bool coupe(int x1,int y1,int dx1,int dy1,int x2,int y2,int dx2,int dy2)
	{
		int u1=x1, v1=y1, u2=x2+dx2, v2=y2+dy2;
		if(u1>x2) u1=x2;
		if(v1>y2) v1=y2;
		if(x1+dx1>u2) u2=x1+dx1;
		if(y1+dy1>v2) v2=y1+dy1;
		if(u2-u1+1<dx1+dx2 && v2-v1+1<dy1+dy2) return true;
		return false;
	}

#ifndef ALLEGRO_WINDOWS
	inline int max(int a,int b)
	{
		if(a>b)
			return a;
		return b;
	}
#endif
public:

	int load_obj(byte *data,int offset,int dec=0);

	void create_from_2d(BITMAP *bmp,float w,float h,float max_h);

	void compute_coord(SCRIPT_DATA *data_s=NULL,VECTOR *pos=NULL,bool c_part=false,int p_tex=0,VECTOR *target=NULL,POINTF *upos=NULL,MATRIX_4x4 *M=NULL,float size=0.0f,VECTOR *center=NULL);

	bool draw(SCRIPT_DATA *data_s=NULL,bool sel_primitive=false,bool alset=false,bool notex=false);

	bool draw_shadow(VECTOR Dir,SCRIPT_DATA *data_s=NULL,bool alset=false);

	bool hit(VECTOR Pos,VECTOR Dir,SCRIPT_DATA *data_s,VECTOR *I,MATRIX_4x4 M);

	void init()
	{
		t_line=NULL;
		line_v_idx[0]=NULL;
		line_v_idx[1]=NULL;
		nb_line=0;
		shadow_index=NULL;
		tcoord=NULL;
		dtex=false;
		pos_from_parent.x=pos_from_parent.y=pos_from_parent.z=0.0f;
		nb_vtx=0;
		nb_prim=0;
		name=NULL;
		next=child=NULL;
		points=NULL;
		p_index=NULL;
		l_index=NULL;
		t_index=NULL;
		nb_p_index=0;
		nb_l_index=0;
		nb_t_index=0;
		N=NULL;
		tex=NULL;
		nb_index=NULL;
		usetex=NULL;
		selprim=-1;
		script_index=-1;
	}

	OBJECT()
	{
		init();
	}

	void destroy()
	{
		if(t_line)			free(t_line);
		if(line_v_idx[0])	free(line_v_idx[0]);
		if(line_v_idx[1])	free(line_v_idx[1]);
		if(shadow_index)	free(shadow_index);
		if(tcoord)			free(tcoord);
		if(dtex)			glDeleteTextures(1,&gltex);
		if(usetex)			free(usetex);
		if(nb_index)		free(nb_index);
		if(tex)				// Ne détruit pas les textures qui le seront par la suite(celles-ci ne sont chargées qu'une fois
			free(tex);		// mais peuvent être utilisées par plusieurs objets
		if(N)				free(N);
		if(points)			free(points);
		if(p_index)			free(p_index);
		if(l_index)			free(l_index);
		if(t_index)			free(t_index);
		if(name)			free(name);
		if(next) {
			next->destroy();
			free(next);
			}
		if(child) {
			child->destroy();
			free(child);
			}
		init();
	}

	~OBJECT()
	{
		destroy();
	}

	inline void Identify(int nb_piece,char **piece_name)			// Identifie les pièces utilisées par le script
	{
		script_index=-1;				// Pièce non utilisée
		for(int i=0;i<nb_piece;i++)
			if(strcasecmp(name,piece_name[i])==0) {		// Pièce identifiée
				script_index=i;
				break;
				}
		if(next)
			next->Identify(nb_piece,piece_name);
		if(child)
			child->Identify(nb_piece,piece_name);
	}

	VECTOR compute_center(int *coef)		// Calcule les coordonnées du centre de l'objet, objets liés compris
	{
		VECTOR center;
		center.x=center.y=center.z=0.0f;
		for(int i=0;i<nb_vtx;i++) {
			(*coef)++;
			center.x+=points[i].x;
			center.y+=points[i].y;
			center.z+=points[i].z;
			}
		if(next) {
			VECTOR c_next=next->compute_center(coef);
			center=center+c_next;
			}
		if(child) {
			int ncoef;
			VECTOR c_child=child->compute_center(coef);
			center=center+c_child;
			}
		return center;
	}

	float compute_size_sq(POINTF center)		// Carré de la taille(on fera une racine après)
	{
		float size=0.0f;
		for(int i=0;i<nb_vtx;i++) {
			float dist=(center>>points[i]).Sq();
			if(size<dist)
				size=dist;
			}
		if(next) {
			float size_next=next->compute_size_sq(center);
			if(size<size_next)
				size=size_next;
			}
		if(child) {
			float size_child=child->compute_size_sq(center);
			if(size<size_child)
				size=size_child;
			}
		return size;
	}

	float print_struct(float Y,float X,FONT *fnt);
};

class MODEL						// Classe pour la gestion des modèles 3D
{
public:
	OBJECT		obj;			// Objet principal du modèle 3D

public:

	VECTOR	center;				// Centre de l'objet pour des calculs d'élimination d'objets
	float	size;				// Taille de la sphère la plus petite dans laquelle tient l'objet

	void init()
	{
		obj.init();
		center.x=center.y=center.z=0.0f;
		size=0.0f;
	}

	MODEL()
	{
		init();
	}

	void destroy()
	{
		obj.destroy();
		init();
	}

	~MODEL()
	{
		destroy();
	}

	int load_3do(byte *data)
	{
		int err=obj.load_obj(data,0);		// Charge les objets composant le modèle
		if(err==0) {
			int coef=0;
			center=obj.compute_center(&coef);
			center=(1.0f/coef)*center;
			POINTF O;
			O.x=O.y=O.z=0.0f;
			size=2.0f*obj.compute_size_sq(O+center);			// On garde le carré pour les comparaisons et on prend une marge en multipliant par 2.0f
			}
		return err;
	}

	inline void create_from_2d(BITMAP *bmp,float w,float h,float max_h)
	{
		obj.create_from_2d(bmp,w,h,max_h);
		int coef=0;
		center=obj.compute_center(&coef);
		center=(1.0f/coef)*center;
		POINTF O;
		O.x=O.y=O.z=0.0f;
		size=2.0f*obj.compute_size_sq(O+center);			// On garde le carré pour les comparaisons et on prend une marge en multipliant par 2.0f
	}

	inline void draw(SCRIPT_DATA *data_s=NULL,bool sel=false,bool notex=false,bool c_part=false,int p_tex=0,VECTOR *target=NULL,POINTF *upos=NULL,MATRIX_4x4 *M=NULL,float Size=0.0f,VECTOR *Center=NULL)
	{
		if(notex)
			glDisable(GL_TEXTURE_2D);
		glEnable(GL_CULL_FACE);
		VECTOR pos;
		pos.x=pos.y=pos.z=0.0f;
		if(notex) {
			float var=fabs(1.0f-(Atimer%MSEC_TO_TIMER(1000))/(float)MSEC_TO_TIMER(500));
			glColor3f(0.0f,var,0.0f);
			}
		else
			glColor3f(1.0f,1.0f,1.0f);
		obj.draw(data_s,sel,false,notex);
		if(c_part)
			obj.compute_coord(data_s,&pos,c_part,p_tex,target,upos,M,Size,Center);
	}

	inline void compute_coord(SCRIPT_DATA *data_s=NULL,MATRIX_4x4 *M=NULL)
	{
		VECTOR pos;
		pos.x=pos.y=pos.z=0.0f;
		obj.compute_coord(data_s,&pos,false,0,NULL,NULL,M);
	}

	inline void draw_shadow(VECTOR Dir,SCRIPT_DATA *data_s=NULL)
	{
		glDisable(GL_TEXTURE_2D);
		obj.draw_shadow(Dir,data_s,false);
	}

	inline bool hit(VECTOR Pos,VECTOR Dir,SCRIPT_DATA *data_s,VECTOR *I,MATRIX_4x4 M)
	{
		return obj.hit(Pos,Dir,data_s,I,M);
	}

	inline void Identify(int nb_piece,char **piece_name)
	{
		obj.Identify(nb_piece,piece_name);
	}

	inline void print_struct(float Y,float X,FONT *fnt)
	{
		obj.print_struct(Y,X,fnt);
	}
};

class MODEL_MANAGER							// Classe pour la gestion des modèles 3D du jeu
{
public:
	int			nb_models;		// Nombre de modèles
	MODEL		*model;			// Tableau de modèles
	char		**name;			// Tableau contenant les noms des modèles

	void init()
	{
		nb_models=0;
		model=NULL;
		name=NULL;
	}

	MODEL_MANAGER()
	{
		init();
	}

	void destroy()
	{
		if(model) {
			for(int i=0;i<nb_models;i++)
				model[i].destroy();
			free(model);
			}
		if(name) {
			for(int i=0;i<nb_models;i++)
				free(name[i]);
			free(name);
			}
		init();
	}

	~MODEL_MANAGER()
	{
		destroy();
	}

	MODEL *get_model(char *nom)
	{
		if(nom==NULL)
			return NULL;
		char vrai_nom[100];
		vrai_nom[0]=0;
		strcat(vrai_nom,"/objects3d/");
		strcat(vrai_nom,nom);
		strcat(vrai_nom,".3do");
		for(int i=0;i<nb_models;i++)
			if(strcasecmp(vrai_nom,name[i])==0)
				return &(model[i]);
		vrai_nom[0]=0;
		strcat(vrai_nom,nom);
		strcat(vrai_nom,".3do");
		for(int i=0;i<nb_models;i++)
			if(strcasecmp(vrai_nom,name[i])==0)
				return &(model[i]);
		vrai_nom[0]=0;
		strcat(vrai_nom,nom);
		for(int i=0;i<nb_models;i++)
			if(strcasecmp(vrai_nom,name[i])==0)
				return &(model[i]);
		return NULL;
	}

	int load_all_in_hpi(char *filename)
	{
		HPI_FILE hpi;
		hpi.load(filename);
		int new_nb_models=hpi.get_number_of_entry("/objects3d");

		if(new_nb_models==-1) {
			hpi.destroy();
			return -1;
			}

		MODEL *n_model=(MODEL*) malloc(sizeof(MODEL)*(nb_models+new_nb_models));
		char **n_name=(char**) malloc(sizeof(char*)*(nb_models+new_nb_models));
		if(model) {
			memcpy(n_model,model,sizeof(MODEL)*nb_models);
			free(model);
			memcpy(n_name,name,sizeof(char*)*nb_models);
			free(name);
			}
		model=n_model;
		name=n_name;
		int i=0;
		for(int e=0;e<new_nb_models;e++) {
			model[i+nb_models].init();
			name[i+nb_models]=hpi.find("/objects3d",e);
			if(get_model(name[i+nb_models])==NULL) {				// Vérifie si le modèle n'est pas déjà chargé
				byte *data=hpi.extract_memory_file(name[i+nb_models]);
				model[i+nb_models].load_3do(data);
				free(data);
				i++;
				}
			}
		nb_models+=i;
		hpi.destroy();

		return 0;
	}

	int load_all()
	{
		int new_nb_models=0;
		char **model_list=file_list("/objects3d",".3do",&new_nb_models);

		if(new_nb_models==-1)
			return -1;

		MODEL *n_model=(MODEL*) malloc(sizeof(MODEL)*(nb_models+new_nb_models));
		char **n_name=(char**) malloc(sizeof(char*)*(nb_models+new_nb_models));
		if(model) {
			memcpy(n_model,model,sizeof(MODEL)*nb_models);
			free(model);
			memcpy(n_name,name,sizeof(char*)*nb_models);
			free(name);
			}
		model=n_model;
		name=n_name;
		int i=0;
		for(int e=0;e<new_nb_models;e++) {
			model[i+nb_models].init();
			name[i+nb_models]=model_list[e];
			if(get_model(name[i+nb_models])==NULL) {				// Vérifie si le modèle n'est pas déjà chargé
				byte *data=load_file(name[i+nb_models]);
				model[i+nb_models].load_3do(data);
				free(data);
				i++;
				}
			}
		nb_models+=i;

		free(model_list);

		return 0;
	}

	inline void create_from_2d(BITMAP *bmp,float w,float h,float max_h,char *filename)
	{
		MODEL *n_model=(MODEL*) malloc(sizeof(MODEL)*(nb_models+1));
		char **n_name=(char**) malloc(sizeof(char*)*(nb_models+1));
		if(model) {
			memcpy(n_model,model,sizeof(MODEL)*nb_models);
			free(model);
			memcpy(n_name,name,sizeof(char*)*nb_models);
			free(name);
			}
		model=n_model;
		name=n_name;
		model[nb_models].init();
		name[nb_models]=strdup(filename);
		model[nb_models].create_from_2d(bmp,w,h,max_h);
		nb_models++;
	}
};

extern MODEL_MANAGER	model_manager;

#endif
