/* knot.c (I guess it's pronounced "nazi") */ #include #include #include #include /* 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();