/* tree.c : SGI: three-dimensional fractal trees */

static const char *Version[] = {
	"@(#) tree.c Copyright (c) 1994 C. Alex. North-Keys",
	"$Grueppe: Talisman $",
	"$Anfang: Mon Sep 19 04:07:33 GMT 1994 $",
	"$Compiliert: "__DATE__" " __TIME__ " $",
	"$Source: /usr/people/erlkonig/zoo/sgi/fractals/tree.c $",
	"$State: $",
	"$Revision: $",
	"$Date: Mon Sep 19 04:07:33 GMT 1994 $",
	"$Author: erlkonig $",
	(const char*)0
	};

/* NOTE: this API (GL) is obsolete - use OpenGL instead */

#include <stdio.h>
#include <gl/gl.h>
#include <gl/device.h>
#include <random.h>
#include "tree.h"

enum
{
	X = 0,
	Y = 1,
	Z = 2,
	XYZ = 3,
};

/* global */
int PrettyFlag = 0;

void NV(const float point[XYZ])
{
	v3f(point);
	if(PrettyFlag)
		n3f(point);
	return;
}
/* base - 3d-coord, scale - magnitude of xyz components outward */
void Plain(const float base[XYZ], float scale)
{
	static const float Blue[] = { 0.0, 0.0, 1.0 };
	static float ground[] = {
		AMBIENT, 0, 0, 0,
		DIFFUSE, 0, .01, .3,
		SPECULAR, .1, .1, .3,
		LMNULL,
	};
	static int initialized = 0;

	if( ! initialized)
	{
		lmdef(DEFMATERIAL, 1004, 0, ground);
		++initialized;
	}
	if(PrettyFlag)
		lmbind(MATERIAL, 1004);
	else
		lmbind(MATERIAL, 0);
	c3f(Blue);

	bgnpolygon();
	{ float trg[3] = { base[X] + scale, base[Y], base[Z] + scale }; NV(trg); }
	{ float trg[3] = { base[X] - scale, base[Y], base[Z] + scale }; NV(trg); }
	{ float trg[3] = { base[X] - scale, base[Y], base[Z] - scale }; NV(trg); }
	{ float trg[3] = { base[X] + scale, base[Y], base[Z] - scale }; NV(trg); }
	endpolygon();

	return;
}

/* base - 3d-coord, scale - magnitude of xyz components */
void Leaf(const float base[XYZ], float scale)
{
	static const float Inner[] = { 0.0, 0.8, 0.4 };
	static const float Outer[] = { 0.0, 0.4, 0.0 };

	bgnpolygon();
	c3f(Outer);
	{ float trg[3] = { base[X] + scale, base[Y], base[Z] }; NV(trg); }
	{ float trg[3] = { base[X], base[Y], base[Z] + scale }; NV(trg); }
	c3f(Inner);
	NV(base);
	endpolygon();

	bgnpolygon();
	c3f(Outer);
	{ float trg[3] = { base[X], base[Y], base[Z] + scale }; NV(trg); }
	{ float trg[3] = { base[X] - scale, base[Y], base[Z] }; NV(trg); }
	c3f(Inner);
	NV(base);
	endpolygon();

	bgnpolygon();
	c3f(Outer);
	{ float trg[3] = { base[X] - scale, base[Y], base[Z] }; NV(trg); }
	{ float trg[3] = { base[X], base[Y], base[Z] - scale }; NV(trg); }
	c3f(Inner);
	NV(base);
	endpolygon();

	bgnpolygon();
	c3f(Outer);
	{ float trg[3] = { base[X], base[Y], base[Z] - scale }; NV(trg); }
	{ float trg[3] = { base[X] + scale, base[Y], base[Z] }; NV(trg); }
	c3f(Inner);
	NV(base);
	endpolygon();

	return;
}

/* src, trg - 3d-coords, rad - radius (to side of sqr Xsection) of branch */
void Branch(const float src[XYZ], const float trg[XYZ], float rad)
{
	/* 8 vertices, 4 faces */
	float fl[3] = { src[X] - rad, src[Y], src[Z] - rad };
	float fr[3] = { src[X] + rad, src[Y], src[Z] - rad };
	float br[3] = { src[X] + rad, src[Y], src[Z] + rad };
	float bl[3] = { src[X] - rad, src[Y], src[Z] + rad };
	float FL[3] = { trg[X] - rad, trg[Y], trg[Z] - rad };
	float FR[3] = { trg[X] + rad, trg[Y], trg[Z] - rad };
	float BR[3] = { trg[X] + rad, trg[Y], trg[Z] + rad };
	float BL[3] = { trg[X] - rad, trg[Y], trg[Z] + rad };

	static const float Brown[] = { 0.3, 0.2, 0.0 };
	static const float DarkBrown[] = { 0.1, 0.16666, 0.0 };
	static float wood[] = {
		AMBIENT, 0, 0, 0,
		DIFFUSE, .2, .1, 0,
		SPECULAR, .0, .0, .0,
		LMNULL,
	};
	
	static int initialized = 0;
	if( ! initialized)
	{
		lmdef(DEFMATERIAL, 1002, 0, wood);
		++initialized;
	}
	if(PrettyFlag)
		lmbind(MATERIAL, 1002);
	else
		lmbind(MATERIAL, 0);

	bgntmesh();
	c3f(Brown);
	NV(fl);	NV(FL);
	c3f(DarkBrown);
	NV(fr);	NV(FR);
	c3f(Brown);
	NV(br); NV(BR);
	c3f(DarkBrown);
	NV(bl); NV(BL);
	c3f(Brown);
	NV(fl);
	endtmesh();
	
	return;
}

/* base - 3d-coord, scale - magnitude of xyz components, depth - ends at 0 */
void Tree(const float base[XYZ], float scale, int depth)
{
	int lift = 30 + scale;
	float rad = scale / 10;

	if(depth > 0)
	{
		float trg[3];
		int i;
		for(i = 0 ; i <= 2 ; ++i) trg[i] = base[i];
		trg[Y] += lift;			/* [Y] holds this value */ 

		trg[X] = base[X] + scale; trg[Z] = base[Z] + scale;
		Branch(base, trg, rad);
		Tree(trg, scale/2, depth - 1);

		trg[X] = base[X] - scale; trg[Z] = base[Z] + scale;
		Branch(base, trg, rad);
		Tree(trg, scale/2, depth - 1);

		trg[X] = base[X] - scale; trg[Z] = base[Z] - scale;
		Branch(base, trg, rad);
		Tree(trg, scale/2, depth - 1);

		trg[X] = base[X] + scale; trg[Z] = base[Z] - scale;
		Branch(base, trg, rad);
		Tree(trg, scale/2, depth - 1);
	}

	{
		static float chlorphyll[] = {
			AMBIENT, .1, .1, .1,
			DIFFUSE, 0, .369, 0,
			SPECULAR, .0, .6, .0,
			LMNULL,
		};
		static int initialized = 0;
		if( ! initialized)
		{
			lmdef(DEFMATERIAL, 1003, 0, chlorphyll);
			++initialized;
		}
	}

	if((int)(base[Y] / 100))
	{
		if(PrettyFlag)
			lmbind(MATERIAL, 1003);
		else
			lmbind(MATERIAL, 0);
		Leaf(base, 4 + scale);
	}

	return;
}

void GetCenteredMouse(long *xp, long *yp)
{
	static Screencoord left, right, bottom, top;
	long originx, originy, mx, my;
	int initialized = 0;

	if( ! initialized)
	{
		getviewport(&left, &right, &bottom, &top);
		getorigin(&originx, &originy);
		++initialized;
	}

	/* origin at bottom left */
	mx = (getvaluator(MOUSEX) - originx);
	my = (getvaluator(MOUSEY) - originy);

	/* now in reference to origin at center of window */
	mx = mx - ((left + right) / 2);
	my = my - ((top + bottom) / 2);

	*xp = mx, *yp = my;

	return;
}

void Pretty(void)
{
	PrettyFlag = ! PrettyFlag;

	if(PrettyFlag)
	{
		subpixel(TRUE);
		blendfunction(BF_SA, BF_MSA);
		pntsmooth(TRUE);
		linesmooth(SML_ON | SML_SMOOTHER | SML_END_CORRECT);
		polysmooth(PYSM_ON);
	}	
	else
	{
		subpixel(FALSE);
		blendfunction(BF_ONE, BF_ZERO);
		pntsmooth(SMP_OFF);
		linesmooth(SML_OFF);
		polysmooth(PYSM_OFF);
	}

	{
		static float lm[] = {
			AMBIENT, 0, 0, 0,
			LOCALVIEWER, 1,
			LMNULL,
		};
		static float lt[] = {
			LCOLOR, 1, 1, 1,
			POSITION, 200, 500, -200, 1, /* 1:point source */
			LMNULL,
		};
		static float mat[] = {
			AMBIENT, .1, .1, .1,
			DIFFUSE, 0, .369, 0,
			SPECULAR, .0, .6, .0,
			LMNULL,
		};

		if(PrettyFlag)
		{
			lmdef(DEFLMODEL, 1001, 0, lm);		lmbind(LMODEL, 1001);
			lmdef(DEFLIGHT, 1001, 0, lt);		lmbind(LIGHT0, 1001);
			lmdef(DEFMATERIAL, 1001, 0, mat);	lmbind(MATERIAL, 1001);
			lmbind(BACKMATERIAL, 1001);
		}
		else
		{
			lmbind(LMODEL, 0);
			lmbind(LIGHT0, 0);
			lmbind(MATERIAL, 0);
			lmbind(BACKMATERIAL, 0);
		}
	}
	return;
}

int main(int ac, char **av)
{
	int size = 800;
	float here[XYZ] = { 0, 500, -1200 };
	float there[XYZ] = { 0, 0, 400 };
	float twist = 0;
	int depth = 0;

	/* initialize graphics */
 	prefsize(1200, 900);
	winopen("tree");
	stereobuffer();
	doublebuffer();
	mmode(MVIEWING);
	{
		long xsize, ysize;
		float aspect;
		getsize(&xsize, &ysize);
		aspect = (float)xsize / (float)ysize;
		fprintf(stderr, "xsize %ld, ysize %ld, aspect %f\n",
				xsize, ysize, aspect);
		perspective(100, aspect, -500, 500);
	}
	RGBmode();


	lsetdepth(getgdesc(GD_ZMAX), getgdesc(GD_ZMIN));
	{
		float parms[] = { 400, 10000, 0.0, 0.0, 0.25 };
		fogvertex(FG_VTX_LIN, parms);
		fogvertex(FG_ON, 0); /* the zero is ignored */
	}
	zbuffer(TRUE);
	
	gconfig();
	qdevice(KEYBD);
	qdevice(LEFTMOUSE);
	qdevice(MIDDLEMOUSE);

	for(;;)
	{
		static int left = 0;
		static int panning = 0, zooming = 0;
		int bail = 0;
		short val;
		Device dev;
		/* clear everything to dark blue */
		czclear(0x00440000, getgdesc(GD_ZMAX));

		leftbuffer(left), rightbuffer(!left);

		pushmatrix();
		{
			static const float zero[XYZ] = { 0, 0, 0 };
			static const float b1[XYZ] = { 300, 0, 100 };
			static const float b2[XYZ] = { -375, 0, 375 };
			static const float b3[XYZ] = { -300, 0, -200 };

			/* choose viewpoint */
			lookat( here[X],  here[Y],  here[Z],
				   there[X], there[Y], there[Z],
				   twist);

			/* make plain and tree */
			Plain(zero, 400);
			Tree(b1, 50, depth - 1);
			Tree(b2, 40, depth - 3);
			Tree(b3, 70, depth - 2);
			Tree(zero, 100, depth);
		}
		popmatrix();
		swapbuffers(); gflush();

		if(panning)
		{
			long mx, my;
			GetCenteredMouse(&mx, &my);
			here[X] += -mx;
			here[Y] += my;
		}

		if(zooming)
		{
			long mx, my;
			GetCenteredMouse(&mx, &my);
			here[Z] += (my - mx) / 2;
		}

		switch((panning || zooming)
			   ? (qtest() ? qread(&val) : TIMER0)
			   : qread(&val))
		{
		  default:
			break;
		  case TIMER0:    break;
		  case LEFTMOUSE: panning = val; break;
		  case MIDDLEMOUSE: zooming = val; break;
		  case KEYBD:
			switch(val)
			{
			  default:
				ringbell();
				break;
			  case 'a': ++depth; break;
			  case 'z': --depth; break;
			  case 'u': here[Y] += 10; break;
			  case 'd': here[Y] -= 10; break;
			  case 'l': here[X] += 10; break;
			  case 'r': here[X] -= 10; break;
			  case 'n': here[Z] += 10; break;
			  case 'f': here[Z] -= 10; break;
			  case 'U': here[Y] += 100; break;
			  case 'D': here[Y] -= 100; break;
			  case 'L': here[X] += 100; break;
			  case 'R': here[X] -= 100; break;
			  case 'N': here[Z] += 100; break;
			  case 'F': here[Z] -= 100; break;
			  case 'p': Pretty(); break;
			  case 'q': gexit(); return 0;
			}
		  case REDRAW: break;	/* will happen without effort from here */ 
		  case INPUTCHANGE: break;
		  case WINQUIT: gexit(); return 0;
		}
	} /* for(;;) */
		
	gexit();					/* shouldn`t ever actually get here*/ 
	return 0;
}
