/*
 * Richard Richter, 97703, 8. zadanie
 *
 * 8. zadanie z PG1, 2000/2001 - 3d graf pomocou maliarovho algoritmu
 */

#include <fox/fx.h>

#define	ROZMER	50

#define dsin(x)	(sin((x)/180.0*PI))
#define dcos(x)	(cos((x)/180.0*PI))
#define d2r(x)	((x)*PI/180)
#define r2d(x)	((x)/PI*180)

typedef struct { FXdouble x,y,z; } Point3D;

// Tu zacina nase hlavne okno
class Priestor : public FXMainWindow {

	FXDECLARE(Priestor)

private:
	FXVerticalFrame		*obsah;		// hlavny ram
	FXCanvas		*plocha;	// plocha pre obraz
	FXImage			*pamat;		// pamat obrazu
	FXStatusbar		*status;	// status, co ineho
	FXMenuPane		*subormenu;	// menu
	FXMenuPane		*pomocmenu;
	Point3D			pole[ROZMER][ROZMER];	// hodnoty funkcie
	FXPoint			ppole[ROZMER][ROZMER];	// funkcia na obrazovke
	FXint			statdirty;	// obnovovanie statu
	FXint			azimut;
	FXint			elevacia;
	FXdouble		sx,sy;		// stred obrazovky
	FXint			lmouse,rmouse;	// tlacitka mysi
	FXdouble		dist,zoom;

protected:
	Priestor() {}

public:
	long onPaint(FXObject*,FXSelector,void *);
	long onResize(FXObject*,FXSelector,void *);
	long onUpdStat(FXObject*,FXSelector,void *);
	long onMBR(FXObject*,FXSelector,void *);
	long onMBRrel(FXObject*,FXSelector,void *);
	long onMBL(FXObject*,FXSelector,void *);
	long onMBLrel(FXObject*,FXSelector,void *);
	long onMMove(FXObject*,FXSelector,void *);

	void VykresliScenu(void);
	void Trans(FXPoint &fxp,Point3D &p);
	void Roto(Point3D &p);

// Spravy naseho kreslitka
	enum {
		ID_PLOCHA=FXMainWindow::ID_LAST,
		ID_HELP,
		ID_STATUS,
		ID_LAST
		};

// Konstruktor a destruktor
	Priestor(FXApp *a);
	~Priestor();

// Inicializacia
	virtual void create();
	};	// Koniec deklaracie triedy


// Mapa udalosti a sprav
FXDEFMAP(Priestor) PriestorMap[] = {
FXMAPFUNC(SEL_PAINT,		Priestor::ID_PLOCHA,	Priestor::onPaint),
FXMAPFUNC(SEL_CONFIGURE,	Priestor::ID_PLOCHA,	Priestor::onResize),
FXMAPFUNC(SEL_RIGHTBUTTONPRESS,	Priestor::ID_PLOCHA,	Priestor::onMBR),
FXMAPFUNC(SEL_RIGHTBUTTONRELEASE,Priestor::ID_PLOCHA,	Priestor::onMBRrel),
FXMAPFUNC(SEL_LEFTBUTTONPRESS,	Priestor::ID_PLOCHA,	Priestor::onMBL),
FXMAPFUNC(SEL_LEFTBUTTONRELEASE,Priestor::ID_PLOCHA,	Priestor::onMBLrel),
FXMAPFUNC(SEL_MOTION,		Priestor::ID_PLOCHA,	Priestor::onMMove),
FXMAPFUNC(SEL_UPDATE,		Priestor::ID_STATUS,	Priestor::onUpdStat),
};

FXIMPLEMENT(Priestor,FXMainWindow,PriestorMap,ARRAYNUMBER(PriestorMap))

// Vytvorenie okna
Priestor::Priestor(FXApp *a):FXMainWindow(a,"Priestor",NULL,NULL,DECOR_ALL,0,0,782,530) {
	// Pruh menu
	FXMenubar *menubar=new FXMenubar(this,LAYOUT_SIDE_TOP|LAYOUT_FILL_X);
	// Menu subor
	subormenu=new FXMenuPane(this);
	new FXMenuTitle(menubar,"&Subor",NULL,subormenu);
	new FXMenuCommand(subormenu,"&Koniec\tCtrl-K",NULL,a,FXApp::ID_QUIT);
	// Menu napovedy (pomoc)
	pomocmenu=new FXMenuPane(this);
	new FXMenuTitle(menubar,"&Pomoc",NULL,pomocmenu,LAYOUT_RIGHT);
	new FXMenuCommand(pomocmenu,"Napoveda\tF1",NULL,this,ID_HELP);

	// Status
	status=new FXStatusbar(this,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X);
	status->getStatusline()->setTarget(this);
	status->getStatusline()->setSelector(ID_STATUS);

	// Zvysok okna
	obsah=new FXVerticalFrame(this,LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0, 0,0,0,0);
	  new FXLabel(obsah,"Tu bude / je obraz",NULL,JUSTIFY_CENTER_X|LAYOUT_FILL_X);
	  new FXHorizontalSeparator(obsah,SEPARATOR_GROOVE|LAYOUT_FILL_X);
	  plocha=new FXCanvas(obsah,this,ID_PLOCHA,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT);

	pamat=new FXImage(a,NULL,BITMAP_OWNED,plocha->getWidth(),plocha->getHeight());

	int i,j;
	FXdouble fx,fy,fz;
	for (i=0; i<ROZMER; i++) for (j=0; j<ROZMER; j++) {
		fx=(i-(ROZMER-1)/2.0)/2;
		fy=(j-(ROZMER-1)/2.0)/2;
		pole[i][j].x=fx;
		pole[i][j].y=fy;
		if (fx==0 && fy==0) fz=40;
		else fz=4*cos(sqrt(2*fx*fx+fy*fy))/sqrt(fx*fx+fy*fy);
		//fz=fx+fy;
		//if (fz<0) fz=-fz;
		pole[i][j].z=fz;
		}
	statdirty=1;
	// azimut, elevacia su nastavene tu
	azimut=1;
	elevacia=20;
	lmouse=0;
	rmouse=0;
	dist=100000;
	zoom=10;
	}

Priestor::~Priestor() {
	delete subormenu;
	delete pomocmenu;
	delete pamat;
	}

// Vytvorenie naseho okna
void Priestor::create() {
	pamat->create();
	FXMainWindow::create();
	VykresliScenu();
	show();
	}


long Priestor::onResize(FXObject*,FXSelector,void *) {
	pamat->destroy();
	delete pamat;
	pamat=new FXImage(getApp(),NULL,BITMAP_OWNED,plocha->getWidth(),plocha->getHeight());
	pamat->create();
	statdirty=1;
	VykresliScenu();
	return 1;
	}

long Priestor::onPaint(FXObject*,FXSelector,void *) {
	FXDCWindow dc(plocha);

	dc.drawImage(pamat,0,0);
	return 1;
	}

// Obnova statusu
long Priestor::onUpdStat(FXObject* sender,FXSelector,void *) {
	char	buf[512];
	int	i;

	if (statdirty) {
		FXStatusline *statusline=(FXStatusline *) sender;
		sprintf(buf,"Azimut %d, elevacia %d, zoom %f - pomer %4.2f",azimut,elevacia,zoom,(double)plocha->getWidth()/plocha->getHeight()*0.75);
		statusline->setText(buf);
		statdirty=0;
		}
	return 1;
	}

// Prave tlacitko mysi na ploche - stlacenie
long Priestor::onMBR(FXObject*,FXSelector,void *ptr) {
	FXEvent *ev=(FXEvent *) ptr;

	rmouse=1;
	return 1;
	}

// Prave tlacitko mysi na ploche - pustenie
long Priestor::onMBRrel(FXObject*,FXSelector,void *ptr) {
	FXEvent *ev=(FXEvent *) ptr;
	FXdouble tmp;

	tmp=zoom/25;
	if (zoom>10) tmp=(int)zoom-zoom+1;
	zoom+=(ev->win_y-ev->last_y)*tmp;
	statdirty=1;
	VykresliScenu();
	rmouse=0;
	return 1;
	}

// Lave tlacitko mysi na ploche - stlacenie
long Priestor::onMBL(FXObject*,FXSelector,void *ptr) {
	FXEvent *ev=(FXEvent *) ptr;

	lmouse=1;
	return 1;
	}

// Lave tlacitko mysi na ploche - pustenie
long Priestor::onMBLrel(FXObject*,FXSelector,void *ptr) {
	FXEvent *ev=(FXEvent *) ptr;

	azimut+=ev->win_x-ev->last_x;
	while (azimut>=360) azimut-=360;
	while (azimut<0) azimut+=360;
	elevacia-=ev->win_y-ev->last_y;
	if (elevacia>90) elevacia=90;
	if (elevacia<-90) elevacia=-90;
	statdirty=1;
	VykresliScenu();
	lmouse=0;
	return 1;
	}

// Pohyb mysi
long Priestor::onMMove(FXObject*,FXSelector,void *ptr) {
	FXEvent *ev=(FXEvent *) ptr;

	if (lmouse) {
		azimut+=ev->win_x-ev->last_x;
		while (azimut>360) azimut-=360;
		while (azimut<0) azimut+=360;
		elevacia-=ev->win_y-ev->last_y;
		if (elevacia>90) elevacia=90;
		if (elevacia<-90) elevacia=-90;
		statdirty=1;
		VykresliScenu();
		}
	if (rmouse) {
		FXdouble tmp;

		tmp=zoom/25;
		if (zoom>10) tmp=(int)zoom-zoom+1;
		zoom+=(ev->win_y-ev->last_y)*tmp;
		statdirty=1;
		VykresliScenu();
		}
	return 1;
	}

// Rotacia
void Priestor::Roto(Point3D &p) {
	Point3D ptmp;

	ptmp=p;
	if (azimut) {
		p.x=ptmp.x*dcos(azimut)-ptmp.y*dsin(azimut);
		p.y=ptmp.x*dsin(azimut)+ptmp.y*dcos(azimut);
		}
	ptmp=p;
	if (elevacia) {
		p.y=ptmp.y*dcos(elevacia)-ptmp.z*dsin(elevacia);
		p.z=ptmp.y*dsin(elevacia)+ptmp.z*dcos(elevacia);
		}
	}

// Transformacia 3d->2d
void Priestor::Trans(FXPoint &fxp,Point3D &p) {
	FXdouble x,y;

        fxp.x=int(-((dist*p.x)/(2*dist+p.y))*zoom+sx);
	fxp.y=int(-((dist*p.z)/(2*dist+p.y))*zoom+sy);
	}

// Vykreslenie sceny
void Priestor::VykresliScenu() {
	FXPoint p2d;
	FXPoint pol4[4];
	Point3D p3d;
	FXDCWindow dcpl(plocha);
	FXDCWindow dc(pamat);
	int i,j,istart,istep,jstart,jstep;

	sx=pamat->getWidth()/2.0;
	sy=pamat->getHeight()/2.0;
	for (i=0;i<ROZMER;i++) for (j=0;j<ROZMER;j++) {
		p3d=pole[i][j];
		Roto(p3d);
		Trans(p2d,p3d);
		ppole[i][j]=p2d;
		}
	dc.setForeground(FXRGB(255,255,255));
	dc.fillRectangle(0,0,pamat->getWidth(),pamat->getHeight());
	dc.setForeground(FXRGB(0,0,0));
	if (azimut>270) {istart=1; jstart=ROZMER-1; istep=1; jstep=-1;}
	else if (azimut>180) {istart=jstart=1; istep=jstep=1;}
	else if (azimut>90) {istart=ROZMER-1; jstart=1; istep=-1; jstep=1;}
	else {istart=ROZMER-1; jstart=ROZMER-1; istep=-1; jstep=-1;}
	for (i=istart;i<ROZMER && i>0;i+=istep) for (j=jstart;j<ROZMER && j>0;j+=jstep) {
		pol4[0]=ppole[i][j];
		pol4[1]=ppole[i][j-1];
		pol4[2]=ppole[i-1][j-1];
		pol4[3]=ppole[i-1][j];
		dc.setForeground(FXRGB(255,255,255));
		dc.fillPolygon(pol4,4);
		dc.setForeground(FXRGB(0,0,0));
		dc.drawLine(ppole[i][j].x,ppole[i][j].y,ppole[i-1][j].x,ppole[i-1][j].y);
		dc.drawLine(ppole[i-1][j].x,ppole[i-1][j].y,ppole[i-1][j-1].x,ppole[i-1][j-1].y);
		dc.drawLine(ppole[i-1][j-1].x,ppole[i-1][j-1].y,ppole[i][j-1].x,ppole[i][j-1].y);
		dc.drawLine(ppole[i][j-1].x,ppole[i][j-1].y,ppole[i][j].x,ppole[i][j].y);
	//	dc.drawPoint(ppole[i][j].x,ppole[i][j].y);
		}
	pamat->render();
	dcpl.drawImage(pamat,0,0);
	}

// Hlavny program
int main(int argc,char **argv) {
	FXApp app("Priestor","Seriozna aplikacia");
	app.init(argc,argv);
	new Priestor(&app);
	app.create();
	return app.run();
	}

