/************************************************************************/
/**                                                                    **/
/**    Starfox Cad Tools - (c) 1994 Dylan Cuthbert (Argonaut Software) **/
/**                                                                    **/
/************************************************************************/

#define _SLOPES_

#include "sfc.h"

char *slopefile;

#define ST_GROUND 0
#define ST_WATER 1
#define ST_ICE 2
#define ST_GRASS 3

char *slopetypes[] =
	{
	"GROUND",
	"WATER",
	"ICE",
	"GRASS",
	NULL
	};


FRPath slopeloadpath=
	{
	"*.txt"
	};

FRPath slopesavepath=
	{
	"*.slo"
	};

struct pt
	{
	int x,y,z;
	};

struct dpt
	{
	int x,y;
	};

struct f
	{
	int colour;
	int numpoints;
	int slopetype;
	int points[50];
	int slopepoly:1;
	int animation:1;
	};

struct df
	{
	int origface;
	int numpoints;
	struct dpt points[50];
	};

int numpoints;

struct f *faces;

int numfaces;

struct df *dfaces=NULL;

int numdfaces;

int vrotx=0,vroty=0,vrotz=0;
int zoom=1;


struct pt **frames;
int numframes;

int frame=0;

int highlightedface;

//-----------------------------------------------------------------------
void SlopeSettings(struct f *face)
	{
	int exitplease=0;
	Window *ss;
	char *list=NULL;
	int n,t;

	for (n=0,t=0;slopetypes[n];n++)
		{
		list = (char *)realloc(list,t+strlen(slopetypes[n])+2);
		strcpy(list+t,slopetypes[n]);
		t += strlen(slopetypes[n])+1;
		}

	list[t] = '\0';

	ss = new Window((GrScreenX()*5)/16,(GrScreenY()*3)/8,(GrScreenX()*11)/16,(GrScreenY()*5)/8,MAGENTA,"Slope Settings",W_BACKINGSTORE|W_MOTIFBORDER);

	ss->FGcolor(BLACK);
	ss->setfont(nicefont);

	new Gadget(ss,2,2,96,96,list,6,G_LIST|G_BOXHIGHLIGHT,0);
	new Gadget(ss,ss->MaxX()-GADGETSIZEX(ss,"SLOPE POLY")-2,2,0,0,"SLOPE POLY",5,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT|G_TOGGLE|((face->slopepoly)?G_TOGGLESTATE:0),KB_ALTP);
	new Gadget(ss,ss->MaxX()-GADGETSIZEX(ss,"OK")-2,ss->MaxY()-GADGETSIZEY(ss),0,0,"OK",0,G_TEXTHIGHLIGHT|G_BOXTEXT,13);

	ss->Highlight(6);

	if (numframes>1)
		{
		new Gadget(ss,ss->MaxX()-GADGETSIZEX(ss,"ANIMATION")-2,2+GADGETSIZEY(ss),0,0,"ANIMATION", 8,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT|G_TOGGLE|((face->animation)?G_TOGGLESTATE:0),KB_ALTA);
		}

	ss->SetSelectedItem(6,face->slopetype);

	ss->flush();

	while (!exitplease)
		{
		Update_Mouse();
		if (ss->mouse())
			{
			if (ss->input.gflags&GF_SELECTED)
				{
				switch(ss->input.gadget)
					{
					case 0:
						exitplease = 1;
						break;
					case 5:
						if (ss->input.gflags&GF_TOGGLE) face->slopepoly = -1;
						else face->slopepoly = 0;
						break;
					case 8:
						if (ss->input.gflags&GF_TOGGLE) face->animation = -1;
						else face->animation = 0;
						break;
					default:
						break;
					}
				}
			}
		}

	face->slopetype = ss->GetSelectedItemNum(6);

	delete ss;
	free(list);
	}
//-----------------------------------------------------------------------
void SaveSlope()
	{
	FILE *ofp;
	char *savefile;
	char rootlabel[MAXFILE+MAXEXT];
	int m,n,fra,tf;
	int width,length,minx,maxx,minz,maxz;
	double nx,ny,nz,x1,x2,x3,y1,y2,y3,z1,z2,z3;
	double unit;
	int cx,cz,cy;
	int xrot,yrot,zrot;
	double d;
	char timestr[64];
	time_t secs_now;
	struct tm *time_now;

	tzset();
	time(&secs_now);
	time_now = localtime(&secs_now);
	strftime(timestr,sizeof(timestr),"%A, %d %B %Y, %I:%M%p",time_now);

	if (!numselected)
		{
		Wait_ok(GrScreenX()/2,GrScreenY()/2,"No slopes selected.\n");
		return;
		}

	fnsplit(slopefile,NULL,NULL,rootlabel,NULL);

	strcpy(rootlabel+strlen(rootlabel),".slo");

	savefile = getfilename("Save Slope Data",&slopesavepath,rootlabel,FR_SETPATH);

	if (!savefile)
		{
		Wait_ok(GrScreenX()/2,GrScreenY()/2,"No file specified.\n");
		return;
		}

	if (ofp = fopen(savefile,"w"))
		{
		fnsplit(savefile,NULL,NULL,rootlabel,NULL);
		
		fprintf(ofp,";ÄÄÄÄÄÄ Created with SFCAD (%s) ÄÄÄÄÄÄ\n",timestr);
		fprintf(ofp,"%s_slo\n",rootlabel);

		fprintf(ofp,"	SLOPES	%d\n",numselected);

		for (m=0;m<numselected;m++)
			{

			fprintf(ofp,"\n");

// Is it an animating face?

			if (faces[dfaces[selected[m]].origface].animation)
				{
				tf = numframes;
				fprintf(ofp,"	SLOPEANIM	%d\n",tf);
				}
			else tf = 1;

			for (fra=0;fra<tf;fra++)
				{
				fprintf(ofp,"	SLOPE	");

// Get the first three points from the face

				x1 = frames[fra][faces[dfaces[selected[m]].origface].points[0]].x;
				y1 = frames[fra][faces[dfaces[selected[m]].origface].points[0]].y;
				z1 = frames[fra][faces[dfaces[selected[m]].origface].points[0]].z;

				x2 = frames[fra][faces[dfaces[selected[m]].origface].points[1]].x;
				y2 = frames[fra][faces[dfaces[selected[m]].origface].points[1]].y;
				z2 = frames[fra][faces[dfaces[selected[m]].origface].points[1]].z;

				x3 = frames[fra][faces[dfaces[selected[m]].origface].points[2]].x;
				y3 = frames[fra][faces[dfaces[selected[m]].origface].points[2]].y;
				z3 = frames[fra][faces[dfaces[selected[m]].origface].points[2]].z;

// Find Centre x,z by averaging all points in the polygon
// Also find the width and height

				cx = 0; cz = 0;
				minx = 32768; maxx = -32768;
				minz = 32768; maxz = -32768;

				for (n=0;n<faces[dfaces[selected[m]].origface].numpoints;n++)
					{
					cx += frames[fra][faces[dfaces[selected[m]].origface].points[n]].x;
					cz += frames[fra][faces[dfaces[selected[m]].origface].points[n]].z;

					if (minx > frames[fra][faces[dfaces[selected[m]].origface].points[n]].x)
						minx = frames[fra][faces[dfaces[selected[m]].origface].points[n]].x;
					if (maxx < frames[fra][faces[dfaces[selected[m]].origface].points[n]].x)
						maxx = frames[fra][faces[dfaces[selected[m]].origface].points[n]].x;
					if (minz > frames[fra][faces[dfaces[selected[m]].origface].points[n]].z)
						minz = frames[fra][faces[dfaces[selected[m]].origface].points[n]].z;
					if (maxz < frames[fra][faces[dfaces[selected[m]].origface].points[n]].z)
						maxz = frames[fra][faces[dfaces[selected[m]].origface].points[n]].z;
					}

				cx /= n; cz /= n;
				width = maxx-minx; length = maxz-minz;

// Calculate the normal

				nx = (y2-y1)*(z3-z2)-(z2-z1)*(y3-y2);
				ny = (z2-z1)*(x3-x2)-(x2-x1)*(z3-z2);
				nz = (x2-x1)*(y3-y2)-(y2-y1)*(x3-x2);

				unit = sqrt(nx*nx+ny*ny+nz*nz);

				nx /= unit;
				ny /= unit;
				nz /= unit;

// Calculate the angle relative to y=0,x=0

				xrot = (int)((acos(nz)*128)/M_PI-90);

// Calculate the angle relative to y=0,z=0

				zrot = (int)((acos(nx)*128)/M_PI-90);

// Calculate plane's distance from the origin

				d = nx*x1+ny*y1+nz*z1;

// Calculate x,y,z of centre of polygon

				cy = (int)((nx*cx+nz*cz-d)/ny);

				nx *= 127;
				ny *= -127;
				nz *= 127;

				fprintf(ofp,"%d,%d,%d,",cx,cy,cz);
				fprintf(ofp,"%d,%d,",width,length);
				fprintf(ofp,"%.f,%.f,%.f,",nx,ny,nz);
				fprintf(ofp,"%d,%d,%.f,",xrot,zrot,d);
				fprintf(ofp,"%s_scale,",rootlabel);
				fprintf(ofp,"%s",slopetypes[faces[dfaces[selected[m]].origface].slopetype]);

				if (faces[dfaces[selected[m]].origface].slopepoly)
					fprintf(ofp,",%s_poly%d_%d",rootlabel,m,fra);

				fprintf(ofp,"\n");
				}
			}

		fprintf(ofp,"\n");

		for (m=0;m<numselected;m++)
			{
			if (faces[dfaces[selected[m]].origface].slopepoly)
				{
// Is it an animating face?

				if (faces[dfaces[selected[m]].origface].animation)
					{
					tf = numframes;
					fprintf(ofp,"	SLOPEANIM	%d\n",tf);
					}
				else tf = 1;

				for (fra=0;fra<tf;fra++)
					{

					fprintf(ofp,"\n%s_poly%d_%d\n	SLOPEPOLY	%d,%s_scale\n",rootlabel,m,fra,faces[dfaces[selected[m]].origface].numpoints+1,rootlabel);
					for (n=faces[dfaces[selected[m]].origface].numpoints;n>=0;n--)
						{
						fprintf(ofp,"	SLOPEPOINT	%d,%d\n",
							frames[fra][faces[dfaces[selected[m]].origface].points[n]].x,
							frames[fra][faces[dfaces[selected[m]].origface].points[n]].z );
						}
					}
				}
			}

		fclose(ofp);
		Wait_ok(GrScreenX()/2,GrScreenY()/2,"Slope successfully saved.\n");
		}
	else
		Wait_ok(GrScreenX()/2,GrScreenY()/2,"Couldn't open file.\n");

	}

//----------------------------------------------------
struct pt Rotate(struct pt *p,int rotx,int roty,int rotz)
	{
	struct pt o,n;
	double rx,ry,rz;

	rx = ((rotx*M_PI)/180);
	ry = ((roty*M_PI)/180);
	rz = ((rotz*M_PI)/180);

// Rotate x and z

	o.x = (int)(p->x * cos(ry)+0.5) + (int)(p->z * sin(ry)+0.5);
	o.y = p->y;
	o.z = (int)(p->z * cos(ry)+0.5) - (int)(p->x * sin(ry)+0.5);

	n.x = o.x; n.y = o.y; n.z = o.z;

// Rotate z and y

	o.x = n.x;
	o.y = (int)(n.y * cos(rx)+0.5) + (int)(n.z * sin(rx)+0.5);
	o.z = (int)(n.z * cos(rx)+0.5) - (int)(n.y * sin(rx)+0.5);

	n.x = o.x; n.y = o.y; n.z = o.z;

// Rotate x and y

	o.x = (int)(n.y * cos(rz)+0.5) + (int)(n.x * sin(rz)+0.5);
	o.y = (int)(n.x * cos(rz)+0.5) - (int)(n.y * sin(rz)+0.5);
	o.z = n.z;

	return o;
	}

//----------------------------------------------------
struct dpt ProjectRotate(struct pt *p,int rotx,int roty,int rotz)
	{
	struct pt o;
	struct dpt d;
	o = Rotate(p,rotx,roty,rotz);

	d.x = (o.x*zoom)/4+tw->SizeX()/2;
	d.y = (-o.y*zoom)/4+tw->SizeY()/2;

	return d;
	}

//----------------------------------------------------
struct df *ProjectFace(struct f *fa,int rotx,int roty,int rotz)
	{
	static struct df dface;
	int n;

	dface.numpoints = fa->numpoints;

	for (n=0;n<fa->numpoints;n++)
		{
		dface.points[n] = ProjectRotate(&(frames[frame][fa->points[n]]),rotx,roty,rotz);
		}

	dface.points[n] = dface.points[0];

	return &dface;
	}

//----------------------------------------------------
int FaceVisibility(struct df *f)
	{
	int dx1,dx2,dy1,dy2;
	if (f->numpoints<3) return 1;

	dx1 = f->points[1].x - f->points[0].x;
	dx2 = f->points[2].x - f->points[0].x;
	dy1 = f->points[1].y - f->points[0].y;
	dy2 = f->points[2].y - f->points[0].y;

	if ((dx2*dy1 - dx1*dy2) < 0) return 1;
	return 0;
	}


//----------------------------------------------------
int WithinFace(int n,int x,int y)
	{
	int m;
	int dx1,dy1,dx2,dy2;

	for (m=0;m<dfaces[n].numpoints; m++)
		{
		dx1 = dfaces[n].points[m+1].x - dfaces[n].points[m].x;
		dy1 = dfaces[n].points[m+1].y - dfaces[n].points[m].y;

		dx2 = x - dfaces[n].points[m].x;
		dy2 = y - dfaces[n].points[m].y;

		if ((dx2*dy1 - dx1*dy2) >= 0) return 0;

		}

	return 1;
	}
//----------------------------------------------------
void DrawFace(int n,int c)
	{
	int m;
	int polygon[20][2];

	if (FindSelected(n) != -1)
		{
		if (faces[dfaces[n].origface].slopepoly) *tw << WT_RED;
		else *tw << WT_GREEN;
		}

	for (m=0;m<dfaces[n].numpoints; m++)
		{
		polygon[m][0] = dfaces[n].points[m].x;
		polygon[m][1] = dfaces[n].points[m].y;
		}

	tw->filledpolygon(dfaces[n].numpoints,polygon);

	if (!c) *tw << WT_WHITE;
	else *tw << WT_YELLOW;
	tw->polygon(dfaces[n].numpoints,polygon);

	}
//----------------------------------------------------
void DrawShape(int rotx,int roty,int rotz)
	{
	int n,m;
	struct df *f;

	if (dfaces) free(dfaces);

	for (n=0,numdfaces=0;n<numfaces;n++)
		{
		f = ProjectFace(&faces[n],rotx,roty,rotz);
		if (FaceVisibility(f))
			{
// If visible add it to the list of visible faces
			f->origface = n;
			dfaces = (struct df *)realloc(dfaces,(numdfaces+1)*sizeof(struct df));
			dfaces[numdfaces++] = *f;
			}
		}

	for (n=0;n<numdfaces;n++)
		{
		*tw << WT_BLACK;
		DrawFace(n,0);
		}

	tw->textxy(0,0);
	tw->textalign(GR_ALIGN_LEFT,GR_ALIGN_TOP);

	if (numframes>1) *tw << WT_BLACK << "Frame " << frame+1 << "/" << numframes << "\n";
	}

//----------------------------------------------------
void CloseSlopes()
	{
	int n;

	delete tw;
	if (faces) free(faces);
	if (selected) free(selected);
	if (slopefile) free(slopefile);
	if (frames)
		{
		for (n=0;n<numframes;n++)
			free(frames[n]);
		free(frames);
		}
	}

//----------------------------------------------------

void OpenSlopes()
	{
	FILE *fp;
	int r,n,fra,num,c,m;
	char temp[80];
	

	frames = NULL; faces = NULL; selected = NULL;
	highlightedface = -1;
	numselected = 0;

	tw = new Window(64,64,mw->MaxX()-maxx-32,GrScreenY()-32,CYAN,"Slope Data",W_BACKINGSTORE|W_MOTIFBORDER);

	tw->setclip(0,0,tw->MaxX(),tw->MaxY()-(GADGETSIZEY(tw))*2-2);
	tw->FGcolor(BLACK);
	tw->setfont(nicefont);
	tw->textalign(GR_ALIGN_LEFT,GR_ALIGN_TOP);
	tw->flush();

	slopefile = getfilename("Load Shape",&slopeloadpath,"",FR_SETPATH);

// Create the gadgets for slope editing
	*tw << WT_BLACK;			
	new Gadget(tw,0,tw->MaxY()-(GADGETSIZEY(tw)*2),0,0,"SAVE",1,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT,KB_ALTS);
	new Gadget(tw,0,tw->MaxY()-(GADGETSIZEY(tw)*1),0,0,"LOAD",2,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT,KB_ALTL);

	new Gadget(tw,96,tw->MaxY()-(GADGETSIZEY(tw)*2),0,0,"ZOOM IN",3,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT,'+');
	new Gadget(tw,96,tw->MaxY()-(GADGETSIZEY(tw)*1),0,0,"ZOOM OUT",4,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT,'-');

	tw->flush();

	if (!slopefile)
		{
		Wait_ok(GrScreenX()/2,GrScreenY()/2,"No file selected.\n");
		}
	else
		{
		slopefile = strdup(slopefile);
		if (fp = fopen(slopefile,"r"))
			{
			r = fscanf(fp,"%80s",temp);
			if ((strcmp(temp,"3DG1") && strcmp(temp,"3DAN")) || r!=1)
				{
				Wait_ok(GrScreenX()/2,GrScreenY()/2,"Unrecognised file format!\n");
				slopefile = NULL;
				}
			else
				{
				fscanf(fp,"%d",&numpoints);

				if (strcmp(temp,"3DAN")==0)
					{
					fscanf(fp,"%d",&numframes);
					*tw << WT_BLACK << numframes << " frames of animation:\n";
					if (numframes>1)
						{
						new Gadget(tw,214,tw->MaxY()-(GADGETSIZEY(tw)*2),0,0,">>",5,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT,'.');
						new Gadget(tw,214,tw->MaxY()-(GADGETSIZEY(tw)*1),0,0,"<<",6,G_TEXTHIGHLIGHT|G_BOXHIGHLIGHT|G_BOXTEXT,',');
						}
					tw->flush();
					}
				else numframes = 1;

				frames = (struct pt **)malloc(sizeof(struct pt *)*numframes);

				*tw << WT_BLACK << numpoints << " points found.\n";

				for (fra=0;fra<numframes;fra++)
					{

// Read in the points

					frames[fra] = (struct pt *)calloc(sizeof(struct pt),numpoints);

					for (n=0;n<numpoints; n++)
						{
						fscanf(fp,"%d %d %d",&(frames[fra][n].x),&(frames[fra][n].y),&(frames[fra][n].z));
						frames[fra][n].x = -frames[fra][n].x;
						frames[fra][n].y = -frames[fra][n].y;
						}
					}

// Now read in the faces

				numfaces = 0;
				while (!feof(fp))
					{
					if (fscanf(fp,"%d",&num)==1)	// read the amount of points in the face in
						{
						tw->printf("Face %d has %d points - ",numfaces,num);
						faces = (struct f *)realloc(faces,(numfaces+1)*sizeof(struct f));
						faces[numfaces].numpoints = num;
						faces[numfaces].slopetype = ST_GROUND;
						faces[numfaces].slopepoly = 0;
						faces[numfaces].animation = (numframes>1)?1:0;
						for (n = 0;n<num;n++)
							{
							fscanf(fp,"%d",&(faces[numfaces].points[n]));
							tw->printf("%s%d",(n)?", ":"",faces[numfaces].points[n]);
							}
						faces[numfaces].points[n] = faces[numfaces].points[0];
						fscanf(fp,"%d",&(faces[numfaces].colour));
						tw->printf(" with colour %d\n",faces[numfaces].colour);
						numfaces++;
						}
					}
				tw->flush();
				Wait_ok(GrScreenX()/2,GrScreenY()/2,"%s loaded successfully.\n",slopefile);
				}
			fclose(fp);
			}
		else
			{
			Wait_ok(GrScreenX()/2,GrScreenY()/2,"Unable to load file!\n");
			slopefile = NULL;
			}
		}
	if (slopefile)
		{
		vrotx = 90; vroty = 180; vrotz = 0;
		frame = 0;
		zoom = 4;
		tw->cls();
		DrawShape(vrotx,vroty,vrotz);
		tw->flush();
		}
	}


//----------------------------------------------------

void DoSlopes()
	{
	int redraw=0;
	int n;

	if (!slopefile) return;
	if (tw->mouse())
		{
		if ((tw->input.gflags&GF_SELECTED))
			{
			switch(tw->input.gadget)
				{
				case 1:
					SaveSlope();
					break;
				case 2:
					CloseMode(MODE_SLOPES);
					OpenMode(MODE_SLOPES);
					return;
				case 3:
					if (zoom<32) zoom++;
					redraw = 1;
					break;
				case 4:
					if (zoom>1) zoom--;
					redraw = 1;
					break;
				case 5:
					if (frame<numframes-1) frame++;
					redraw = 1;
					break;
				case 6:
					if (frame>0) frame--;
					redraw = 1;
					break;
				default:
					break;
				}
			}

		if ((((tw->input.flags&M_BUTTON_DOWN) && (tw->input.buttons&M_LEFT)) || ((tw->input.flags&M_KEYPRESS) && (tw->input.key == 13) )) && (highlightedface != -1))
			{
			if (FindSelected(highlightedface) == -1)
				{
				AddSelected(highlightedface);
				SlopeSettings(&faces[dfaces[highlightedface].origface]);
				}
			else DelSelected(highlightedface);
			redraw = 1;
			}
		
// Redraw the 3d shape
		if (redraw)
			{
			tw->cls();
			*tw << WT_BLACK;
			DrawShape(vrotx,vroty,vrotz);
			tw->flush();
			highlightedface = -1;
			redraw = 0;
			}

// If space is pressed then highlight the faces in order

		if ((tw->input.flags&M_KEYPRESS) && (tw->input.key=='n'))
			{
			if (highlightedface != -1)
				{
				*tw << WT_BLACK;
				DrawFace(highlightedface,0);
				}
			if (++highlightedface>=numdfaces) highlightedface = 0;
			*tw << WT_BLUE;
			DrawFace(highlightedface,1);

			tw->textxy(0,tw->textheight());
			tw->textalign(GR_ALIGN_LEFT,GR_ALIGN_TOP);
			*tw << WT_BLACK << "Face " << dfaces[highlightedface].origface << "/" << numfaces << "\n";

			tw->flush();
			}

// If the mouse is moved change the highlighted face

		if ((tw->input.flags&M_MOTION))
			{
			for (n=0;n<numdfaces;n++)
				{
				if (WithinFace(n,tw->input.x,tw->input.y))
					{
					if (n!=highlightedface)
						{
						if (highlightedface!= -1)
							{
							*tw << WT_BLACK;
							DrawFace(highlightedface,0);
							}
						*tw << WT_BLUE;
						DrawFace(n,1);

						tw->textxy(0,tw->textheight());
						tw->textalign(GR_ALIGN_LEFT,GR_ALIGN_TOP);
						*tw << WT_BLACK << "Face " << dfaces[highlightedface].origface << "/" << numfaces << "     \n";

						tw->flush();
						highlightedface = n;
						}
					return;
					}
				}
			}
		}
	}
