/* knot.c (I guess it's pronounced "nazi") */

#include <allegro.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* XXX: These are good for adjustin' */
#define TEXTURE_W 256
#define TEXTURE_H 256

/* XXX: Number of steps for the camera */
#define NUM_STEPS 2048

/* NUM_RINGS and NUM_VERTICES include the guard point, so say one more than you
 * really want. */
#define NUM_RINGS 128	/* XXX: Number of rings along the tube */
#define NUM_VERTICES 8	/* XXX: Number of vertices around a ring */

typedef struct stnb_t {
	float sx, sy, sz;	/* Position vector */
	float tx, ty, tz;	/* Tangent vector */
	float nx, ny, nz;	/* Normal vector */
	float bx, by, bz;	/* Binormal vector */
} stnb_t;

/* Finds the position, tangent, normal, and binormal vectors at time 't'.
 * t: Time value around curve (period: 2 * pi).
 * *sx, *sy, *sz: Position vector.
 * *tx, *ty, *tz: Tangent vector.
 * *nx, *ny, *nz: Normal vector.
 * *bx, *by, *bz: Binormal vector. */
void calc_stnb(float t, float* sx, float* sy, float* sz, float* tx, float* ty,
float* tz, float* nx, float* ny, float* nz, float* bx, float* by, float* bz)
{
	/* XXX: These can be adjusted */
	const float A = 1.0;
	const float B = 0.5;
	const float C = 3.0;
	const float D = 2.0;

	*sx = (A + B * sin(C * t)) * sin(D * t);
	*sy = A * cos(C * t);
	*sz = (A + B * sin(C * t)) * cos(D * t);

	*tx = B * C * cos(D * t) * sin(C * t) + D * cos(D * t) * (B * sin(C *
			t) + A);
	*ty = -B * C * sin(C * t);
	*tz = B * C * cos(D * t) * cos(C * t) - D * sin(D * t) * (B * sin(C *
			t) + A);
	normalize_vector_f(tx, ty, tz);

	*nx = 2 * B * C * D * cos(D * t) * cos(C * t) - sin(D * t) * (B * (C *
			C + D * D) * sin(C * t) + A * D * D);
	*ny = -B * C * C * cos(C * t);
	*nz = -2 * B * C * D * sin(D * t) * cos(C * t) - cos(D * t) * (B * (C *
			C + D * D) * sin(C * t) + A * D * D);
	normalize_vector_f(nx, ny, nz);

	cross_product_f(*tx, *ty, *tz, *nx, *ny, *nz, bx, by, bz);
}

/* Finds all the position, tangent, normal, and binormal vectors around a
 * curve, and makes a list of vertices of a tube encircling the curve.
 * stnb: Array of stnb_t structs.
 * sc: Number of elements in 'stnb'.
 * v: 2d array of V3D_f structs.
 * rc: Number of rings (major axis in 'v').
 * vc: Number of vertices in a ring (minor axis in 'v'). */
void calc_stnbv(stnb_t* stnb, int sc, V3D_f** v, int rc, int vc)
{
	/* XXX: This is the tube radius */
	const float R = 0.0625;

	stnb_t* stnb_p, *stnb_e;
	V3D_f** v_pp, **v_ee, *v_p, *v_e;

	float i, j;

	stnb_p = stnb; stnb_e = stnb + sc;
	for (i = 0; stnb_p != stnb_e; i += 2 * M_PI / sc, stnb_p++)
		calc_stnb(i, &stnb_p->sx, &stnb_p->sy, &stnb_p->sz,
				&stnb_p->tx, &stnb_p->ty, &stnb_p->tz,
				&stnb_p->nx, &stnb_p->ny, &stnb_p->nz,
				&stnb_p->bx, &stnb_p->by, &stnb_p->bz);

	v_pp = v; v_ee = v_pp + rc;
	for (i = 0; v_pp != v_ee; i += 2 * M_PI / (rc - 1), v_pp++) {
		float sx, sy, sz, tx, ty, tz, nx, ny, nz, bx, by, bz;

		calc_stnb(i, &sx, &sy, &sz, &tx, &ty, &tz, &nx, &ny, &nz,
				&bx, &by, &bz);

		v_p = *v_pp; v_e = v_p + vc;
		for (j = 0; v_p != v_e; j += 2 * M_PI / (vc - 1), v_p++) {
			v_p->x = R * (cos(j) * nx + sin(j) * bx) + sx;
			v_p->y = R * (cos(j) * ny + sin(j) * by) + sy;
			v_p->z = R * (cos(j) * nz + sin(j) * bz) + sz;
		}
	}
}

/* Transforms a 2d list of points to camera space.
 * camera: Transformation matrix.
 * vw: 2d array of V3D_f structs in world coordinates.
 * vc: 2d array of V3D_f structs to fill with camera coordinates.
 * rc: Number of rings (major axis in 'vw').
 * vcount: Number of vertices per ping (minor axis in 'vw').
 * texture: Texture with which to paint tube.
 * tfx: Number of times texture repeats around the vertices of the tube.
 * tpy: Number of rings across which texture repeats. */
void to_camera(MATRIX_f* camera, V3D_f** vw, V3D_f** vc, int rc, int vcount,
BITMAP* texture, float tfx, float tpy)
{
	V3D_f** vw_pp, **vw_ee, *vw_p, *vw_e, **vc_pp, *vc_p;
	float tu, tus = texture->w * tfx / (vcount - 1);
	float tv, tvs = texture->h / tpy;

	vc_pp = vc; vw_pp = vw; vw_ee = vw_pp + rc; tv = 0.0;
	while (vw_pp != vw_ee) {
		vc_p = *vc_pp; vw_p = *vw_pp; vw_e = vw_p + vcount; tu = 0.0;
		while (vw_p != vw_e) {
			apply_matrix_f(camera, vw_p->x, vw_p->y, vw_p->z,
					&vc_p->x, &vc_p->y, &vc_p->z);
			vc_p->u = tu; vc_p->v = tv;
			vw_p++; vc_p++;
			tu += tus;
		}
		vw_pp++; vc_pp++;
		tv += tvs;
	}
}

/* Adds a quad to the render list.
 * texture: BITMAP to texture the quad with.
 * a, b, c, d: Corner vertices of the quad. */
void render_quad(BITMAP* texture, const V3D_f* a, const V3D_f* b,
const V3D_f* c, const V3D_f* d)
{
	static virgin = 1;
	static const V3D_f* vtx_in[4];
	static V3D_f vtx_pool[32];
	static V3D_f* vtx_out[16];
	static V3D_f* tmp1[16];
	static int tmp2[16];

	int vc;
	int i;

	if (virgin) {
		/* Prepare the static variables */
		for (i = 0; i < 16; i++) {
			vtx_out[i] = &vtx_pool[i];
			tmp1[i] = &vtx_pool[i + 16];
		}
		virgin = 0;
	}

	/* Do a little clipping of our own */
	if (a->z <= 0 && b->z <= 0 && c->z <= 0 && d->z <= 0) return;

	/* Use Allegro's clip function */
	vtx_in[0] = a; vtx_in[1] = b; vtx_in[2] = c; vtx_in[3] = d;
	vc = clip3d_f(POLYTYPE_PTEX, 0.0, 1.6, 4, vtx_in, vtx_out, tmp1, tmp2);
	if (vc < 3) return;

	/* Project to screen space */
	for (i = 0; i < vc; i++)
		persp_project_f(vtx_out[i]->x, vtx_out[i]->y, vtx_out[i]->z,
				&vtx_out[i]->x, &vtx_out[i]->y);

	/* Throw out polygons facing the wrong way */
	if (polygon_z_normal_f(vtx_out[0], vtx_out[1], vtx_out[2]) <= 0)
		return;

	/* Add to the scene's render list */
	scene_polygon3d_f(POLYTYPE_PTEX, texture, vc, vtx_out);
}

int main(int argc, char** argv)
{
	int mickey_x = 0, mickey_y = 0;
	float x_rot = 0.0, y_rot = 0.0;
	float tfx = 3.0, tpy = 1.0;
	
	int done = 0;

	int x, y;

	stnb_t stnb[NUM_STEPS];
	V3D_f** vw, **vc;

	BITMAP* buffer, *texture;

	/* Camera position */
	int t = 0;

	MATRIX_f camera, rotate;

	vw = malloc(sizeof(V3D_f*) * NUM_RINGS);
	vc = malloc(sizeof(V3D_f*) * NUM_RINGS);
	for (x = 0; x < NUM_RINGS; x++) {
		vw[x] = malloc(sizeof(V3D_f) * NUM_VERTICES);
		vc[x] = malloc(sizeof(V3D_f) * NUM_VERTICES);
	}

	allegro_init();
	install_keyboard();
	install_mouse();
	create_scene(32768, 16384);

	if (desktop_color_depth() != 0) set_color_depth(desktop_color_depth());
	else set_color_depth(8);
	/* XXX: Change this to AUTODETECT_WINDOWED if you want to run in a
	 * window rather than fullscreen */
	set_gfx_mode(GFX_AUTODETECT, 320, 240, 0, 0);
	set_palette(default_palette);

	buffer = create_bitmap(SCREEN_W, SCREEN_H);
	set_projection_viewport(0, 0, SCREEN_W, SCREEN_H);

	/* Generate texture */
	/* XXX: Try making different textures for more fun */
	if (argc == 1) {
		texture = create_bitmap(TEXTURE_W, TEXTURE_H);
		for (y = 0; y < TEXTURE_H; y++)
			for (x = 0; x < TEXTURE_W; x++) {
				int v = (sin((y -
					sin(x * M_PI * 2 / TEXTURE_W) * 16) *
					M_PI * 4 / TEXTURE_H) + 1) *
					90;
				putpixel(screen, x, y, makecol(
						v + (rand() & 31),
						v + (rand() & 31),
						v + (rand() & 31)));
				putpixel(texture, x, y, makecol(
						v + (rand() & 31),
						v + (rand() & 31),
						v + (rand() & 31)));
			}
	} else {
		PALETTE dummy;
		BITMAP* unscaled;
		texture = create_bitmap(TEXTURE_W, TEXTURE_H);
		unscaled = load_bitmap(argv[1], dummy);
		if (unscaled == NULL) return EXIT_FAILURE;
		stretch_blit(unscaled, texture, 0, 0, unscaled->w, unscaled->h,
				0, 0, TEXTURE_W, TEXTURE_H);
	}

	/* Precalculate all the vertices and camera positions */
	calc_stnbv(stnb, NUM_STEPS, vw, NUM_RINGS, NUM_VERTICES);

	while (!done) {
		float sx, sy, sz, tx, ty, tz, nx, ny, nz, bx, by, bz;
		float tx2, ty2, tz2;
		int i, j;

		/* clear_to_color(buffer, black); */
		clear_scene(buffer);

		/* Grab the stnb vectors for this camera position */
		sx = stnb[t].sx; sy = stnb[t].sy; sz = stnb[t].sz;
		tx = stnb[t].tx; ty = stnb[t].ty; tz = stnb[t].tz;
		nx = stnb[t].nx; ny = stnb[t].ny; nz = stnb[t].nz;
		bx = stnb[t].bx; by = stnb[t].by; bz = stnb[t].bz;

		/* Rotate */
		get_vector_rotation_matrix_f(&rotate, nx, ny, nz, y_rot);
		apply_matrix_f(&rotate, tx, ty, tz, &tx2, &ty2, &tz2);
		get_vector_rotation_matrix_f(&rotate, bx, by, bz, x_rot);
		apply_matrix_f(&rotate, tx2, ty2, tz2, &tx, &ty, &tz);

		/* Find the camera matrix */
		get_camera_matrix_f(&camera,	/* Destination MATRIX_f */
				sx, sy, sz,	/* Position */
				tx, ty, tz,	/* Front vector */
				nx, ny, nz,	/* Up vector */
				64.0,		/* Field of view (256 = 2pi) */
				1.0);		/* Aspect ratio */

		/* Go to camera coordinates */
		to_camera(&camera, vw, vc, NUM_RINGS, NUM_VERTICES,
				texture, tfx, tpy);

		/* Create quad render list */
		for (i = 0; i < NUM_RINGS - 1; i++)
			for (j = 0; j < NUM_VERTICES - 1; j++)
				render_quad(texture,
						&vc[i][j],
						&vc[i][j + 1],
						&vc[i + 1][j + 1],
						&vc[i + 1][j]);

		/* Do the display */
		render_scene();

		vsync();
		blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);

		/* Mouse and key bindings */
		get_mouse_mickeys(&mickey_x, &mickey_y);
		if (keypressed()) {
			/* int update_list = 0; */
			if (key[KEY_LEFT]) mickey_x -= 5;
			if (key[KEY_RIGHT]) mickey_x += 5;
			if (key[KEY_UP]) mickey_y -= 5;
			if (key[KEY_DOWN]) mickey_y += 5;

			/* XXX: Uncomment all the comments down here to
			 * activate more keys, but this code doesn't work very
			 * well, so do it at your own risk */
			switch (readkey() >> 8) {
			case KEY_ESC: done = 1; break;
			case KEY_SPACE: x_rot = 0.0; y_rot = 0.0; break;
			case KEY_Y: tfx--; break;
			case KEY_H: tfx++; break;
			case KEY_U: tpy--; break;
			case KEY_J: tpy++; break;
			/* case KEY_Q: A += 0.01; update_list = 1; break;
			case KEY_A: A -= 0.01; update_list = 1; break;
			case KEY_W: B += 0.01; update_list = 1; break;
			case KEY_S: B -= 0.01; update_list = 1; break;
			case KEY_E: C++; update_list = 1; break;
			case KEY_D: C--; update_list = 1; break;
			case KEY_R: D++; update_list = 1; break;
			case KEY_F: D--; update_list = 1; break;
			case KEY_T: R += 0.00125; update_list = 1; break;
			case KEY_G: R -= 0.00125; update_list = 1;break; */
			}

			/* if (update_list) calc_stnbv(stnb, NUM_STEPS, vw,
					NUM_RINGS, NUM_VERTICES); */
		}

		y_rot += mickey_x * M_PI / 8;
		x_rot -= mickey_y * M_PI / 8;

		t++;
		if (t == NUM_STEPS) t = 0;
	}

	destroy_bitmap(texture);
	destroy_bitmap(buffer);
	destroy_scene();

	return EXIT_SUCCESS;
}
END_OF_MAIN();

