/* Terminality - a portable terminal handling library * Copyright (C) 1999-2002, Emil Mikulic. * This is LGPL - look at COPYING.LIB */ /* Project: Terminality * File: demo3d.c * Author: Emil Mikulic * Description: Textmode 3D demo */ /* See the notes on CP437 in precalc.c */ //#define ITEM_NUM /* debugging purposes - show demo item and frame */ #include #include #include #include #include #include #include #include "precalc.h" const char demo3d_rcsid[] = "$Id: demo3d.c,v 1.2 2002/07/27 07:10:12 darkmoon Exp $"; typedef double val; #define PI 3.1415926535 __inline val sqr(const val x) { return x*x; } typedef val vect[3]; #define NO_INTERSECTION 0.0 __inline void vect_add(vect dest, const vect src) { dest[0] += src[0]; dest[1] += src[1]; dest[2] += src[2]; } __inline void vect_sub(vect dest, const vect src) { dest[0] -= src[0]; dest[1] -= src[1]; dest[2] -= src[2]; } __inline void vect_mult(vect dest, const val k) { dest[0] *= k; dest[1] *= k; dest[2] *= k; } __inline void vect_div(vect dest, const val k) { dest[0] /= k; dest[1] /= k; dest[2] /= k; } #define vect_dot(a,b) (a[0]*b[0] + a[1]*b[1] + a[2]*b[2]) #define vect_length(v) sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) __inline void vect_make(vect dest, const val x, const val y, const val z) { dest[0] = x; dest[1] = y; dest[2] = z; } __inline void vect_set(vect dest, const vect src) { dest[0] = src[0]; dest[1] = src[1]; dest[2] = src[2]; } __inline void vect_normal(vect dest) { val tmp = vect_length(dest); dest[0] /= tmp; dest[1] /= tmp; dest[2] /= tmp; } /* always: P = eye + dir * lambda */ __inline void vect_P(vect P, const vect eye, const vect dir, const val lambda) { P[0] = eye[0] + dir[0] * lambda; P[1] = eye[1] + dir[1] * lambda; P[2] = eye[2] + dir[2] * lambda; } __inline void vect_reflect(vect R, const vect I, const vect N) { /* ^ ^ * R = I - 2N * (I . N) * ~ ~ ~ ~ */ val tmp = 2.0 * vect_dot(I, N); R[0] = I[0] - N[0] * tmp; R[1] = I[1] - N[1] * tmp; R[2] = I[2] - N[2] * tmp; } /* rotation: * x2 = x1 cos theta - y1 sin theta * y2 = y1 cos theta + x1 sin theta */ __inline void vect_rot_z(vect dest, const val theta) { val sinth = sin(theta); val costh = cos(theta); val x = dest[0], y = dest[1]; dest[0] = x * costh - y * sinth; dest[1] = y * costh + x * sinth; } __inline void vect_rot_x(vect dest, const val theta) { val sinth = sin(theta); val costh = cos(theta); val y = dest[1], z = dest[2]; dest[1] = y * costh - z * sinth; dest[2] = z * costh + y * sinth; } __inline void vect_rot_y(vect dest, const val theta) { val sinth = sin(theta); val costh = cos(theta); val x = dest[0], z = dest[2]; dest[0] = x * costh - z * sinth; dest[2] = z * costh + x * sinth; } typedef val coord[2]; __inline void make_coord(coord dest, const vect v) { dest[0] = v[0] / v[2]; dest[1] = v[1] / v[2]; } // Screen buffer unsigned char *buf; // Wanna use all possible colors? int fullcolor = 1; // Fade amount int fade = 256; // 256 = 100%, 0 = 0% brightness, negative is flash // Put pixel for AA line void putaapixel(int x, int y, int c) { int ofs, pixel; // Make sure we're inside the screen if ((x<1) || (y<1) || (x>CON_COLS) || (y>CON_ROWS)) return; // Put pixel (additive) ofs = CON_COLS * y + x; pixel = 255-(int)buf[ofs]; pixel = pixel + (255 - c); if (pixel>255) pixel = 255; buf[ofs] = 255-(char)pixel; } // Render buf[] to the screen void render_buf(void) { int i; // Handle interrupt if (keypressed()) raise(SIGTERM); // Clear screen clrscr(); // Render image to screen for (i=0; i= 0) { c = (255 - c) * fade / 256; c = 255 - c; } if (fade < 0) { c = (255 - c) - fade; if (c > 256) c = 256; c = 255 - c; } } if (fullcolor) { bgcolor(Black); switch (c/23) { case 0: bgcolor(LightGray); fgcolor(White); writech(BLOCK_4); break; case 1: bgcolor(LightGray); fgcolor(White); writech(BLOCK_3); break; case 2: bgcolor(LightGray); fgcolor(White); writech(BLOCK_2); break; case 3: bgcolor(LightGray); fgcolor(White); writech(BLOCK_1); break; case 4: fgcolor(LightGray); writech(BLOCK_4); break; case 5: fgcolor(LightGray); writech(BLOCK_3); break; case 6: fgcolor(LightGray); writech(BLOCK_2); break; case 7: fgcolor(DarkGray); writech(BLOCK_4); break; case 8: fgcolor(DarkGray); writech(BLOCK_3); break; case 9: fgcolor(DarkGray); writech(BLOCK_2); break; case 10: fgcolor(DarkGray); writech(BLOCK_1); break; case 11: writech(' '); } } else { switch (c >> 6) { case 0: writech(BLOCK_4); break; case 1: writech(BLOCK_3); break; case 2: writech(BLOCK_2); break; case 3: writech(BLOCK_1); } } } // Clear buffer for (i=0; i y1) { temp = y0; y0 = y1; y1 = temp; temp = x0; x0 = x1; x1 = temp; } /* Draw the initial pixel, which is always exactly intersected by the line and so needs no weighting */ putaapixel(x0, y0, basecolor); deltax = x1-x0; if (deltax >= 0) { xdir = 1; } else { xdir = -1; deltax = -deltax; // make DeltaX positive } /* Special-case horizontal, vertical, and diagonal lines, which require no weighting because they go right through the center of every pixel */ deltay = (y1-y0); if (deltay == 0) { // Horizontal line deltax--; while (deltax > 0) { x0 = x0 + xdir; putaapixel(x0, y0, basecolor); deltax=deltax-1; } return; } if (deltax == 0) { // Vertical line do { y0++; putaapixel(x0, y0, basecolor); deltay--; } while (deltay); return; }; if (deltax == deltay) { // Diagonal line do { x0 = x0 + xdir; y0 = y0 + 1; putaapixel(x0, y0, basecolor); deltay = deltay-1; } while (deltay); return; }; // Line is not horizontal, diagonal, or vertical erracc = 0; // initialize the line error accumulator to 0 // # of bits by which to shift ErrorAcc to get intensity level intensityshift = 16 - intensitybits; // Mask used to flip all bits in an intensity weighting, producing the // result (1 - intensity weighting) weightingcomplementmask = numlevels - 1; // Is this an X-major or Y-major line? if (deltay > deltax) { /* Y-major line; calculate 16-bit fixed-point fractional part of a pixel that X advances each time Y advances 1 pixel, truncating the result so that we won't overrun the endpoint along the X axis */ erradj = (deltax << 16) / deltay; // Draw all pixels other than the first and last deltay = deltay-1; while (deltay>0) { // remember current accumulated error erracctmp = erracc; // calculate error for next pixel erracc = (erracc+erradj) % 65536; // The error accumulator turned over, // so advance the X coord if (erracc <= erracctmp) x0 += xdir; y0++; // Y-major, so always advance Y /* The IntensityBits most significant bits of ErrAcc give us the intensity weighting for this pixel, and the complement of the weighting for the paired pixel */ weighting = erracc >> intensityshift; putaapixel(x0, y0, basecolor + weighting); putaapixel(x0 + xdir, y0, basecolor + (weighting ^ weightingcomplementmask)); deltay--; } /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */ putaapixel(x1, y1, basecolor); return; } else { /* It's an X-major line; calculate 16-bit fixed-point fractional part of a pixel that Y advances each time X advances 1 pixel, truncating the result to avoid overrunning the endpoint along the X axis */ erradj = deltay << 16; erradj = (int)( (val)erradj / (val)deltax ); // Draw all pixels other than the first and last deltax--; while (deltax) { // remember currrent accumulated error erracctmp = erracc; // calculate error for next pixel erracc = (erracc + erradj) % 65536; // The error accumulator turned over, // so advance the Y coord if (erracc <= erracctmp) y0++; x0 += xdir; // X-major, so always advance X /* The IntensityBits most significant bits of ErrAcc give us the intensity weighting for this pixel, and the complement of the weighting for the paired pixel */ weighting = erracc >> intensityshift; putaapixel(x0, y0, basecolor + weighting); putaapixel(x0, y0 + 1, basecolor + (weighting ^ weightingcomplementmask)); deltax--; } /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */ putaapixel(x1, y1, basecolor); } } val xsize, ysize, xofs; // Project cartesian 2D points to screen and draw line void coordline(const coord p1, const coord p2) { int x1, y1, x2, y2; x1 = (int)(p1[0] * xsize + xofs); y1 = (int)(p1[1] * ysize + ysize); x2 = (int)(p2[0] * xsize + xofs); y2 = (int)(p2[1] * ysize + ysize); aaline(x1, y1, x2, y2); } // Framerate delay int framerate; // 1: Fade the line in and spin it half a circle void item1(void) { vect p1, p2; coord t1, t2; int frame; for (frame = 1; frame < 128; frame++) { // Construct vect_3ds - line grows outward vect_make(p1, (frame > 64)?1.0:(val)frame / 64.0, 0, 0); vect_make(p2, (frame > 64)?-1.0:(val)frame / -64.0, 0, 0); // Set fade amount fade = ((frame > 64) ? 256 : frame*4); // Transform #define trans(v) vect_rot_z(v, (val)frame / 64.0 * PI); \ v[2] += 2.0; trans(p1); trans(p2); make_coord(t1, p1); make_coord(t2, p2); // Draw coordline(t1,t2); render_buf(); #ifdef ITEM_NUM normvideo(); writexy(1,1,"1: %d", frame); #endif update(); delay(framerate); } } // 2: Split the line into a triangle while spinning Z void item2(void) { vect p1, p2, p3; coord t1, t2, t3; int frame; for (frame = 0; frame < 64; frame++) { // Build triangle vect_make(p1, -1.0, (val)frame/-64.0, 0.0); vect_make(p2, 1.0, (val)frame/-64.0, 0.0); vect_make(p3, 0, (val)frame/64.0, 0); // Transform #define trans(v) vect_rot_z(v, (val)frame / 64.0 * PI); \ v[2] += 2.0; trans(p1); trans(p2); trans(p3); #undef trans make_coord(t1, p1); make_coord(t2, p2); make_coord(t3, p3); // Draw coordline(t1,t2); coordline(t1,t3); coordline(t2,t3); render_buf(); #ifdef ITEM_NUM normvideo(); writexy(1,1,"2: %d", frame); #endif update(); delay(framerate); } } // 3: Split triangle into rectangle while spinning Z void item3(void) { vect p1, p2, p3, p4; coord t1, t2, t3, t4; int frame; for (frame = 0; frame < 64; frame++) { // Build two triangle points that split apart vect_make(p1, -1.0, 1.0, 0.0); vect_make(p2, 1.0, 1.0, 0.0); vect_make(p3, (val)frame/64.0, -1.0, 0.0); vect_make(p4, (val)frame/-64.0, -1.0, 0.0); // Transform #define trans(v) vect_rot_z(v, (val)frame / 64.0 * PI); \ v[2] += 2.0; trans(p1); trans(p2); trans(p3); trans(p4); #undef trans make_coord(t1, p1); make_coord(t2, p2); make_coord(t3, p3); make_coord(t4, p4); // Draw coordline(t1,t2); coordline(t2,t3); coordline(t3,t4); coordline(t4,t1); render_buf(); #ifdef ITEM_NUM normvideo(); writexy(1,1,"3: %d", frame); #endif update(); delay(framerate); } } // 4: Reveal that rectangle is actually a cube and flash the screen void item4(void) { vect p[8]; coord t[8]; int frame; for (frame = 0; frame <= 128; frame++) { int i; val mult, offset; vect_make(p[0], -1.0, 1.0, -1.0); vect_make(p[1], 1.0, 1.0, -1.0); vect_make(p[2], 1.0,-1.0, -1.0); vect_make(p[3], -1.0,-1.0, -1.0); mult = (frame > 64) ? 1.0 : ((val)frame/64.0); offset = -1.0 + (mult * 2.0); vect_make(p[4], -1.0, 1.0, offset); vect_make(p[5], 1.0, 1.0, offset); vect_make(p[6], 1.0,-1.0, offset); vect_make(p[7], -1.0,-1.0, offset); #define trans(v) vect_rot_z(v, (val)frame / 64.0 * PI); \ vect_rot_x(v, (val)frame / 128.0 * mult * PI); \ vect_rot_y(v, (val)frame / 128.0 * mult * PI); \ v[2] += 3.0; for (i=0; i<8; i++) { trans(p[i]); make_coord(t[i], p[i]); } #undef trans // Draw coordline(t[0], t[1]); coordline(t[1], t[2]); coordline(t[2], t[3]); coordline(t[3], t[0]); coordline(t[4], t[5]); coordline(t[5], t[6]); coordline(t[6], t[7]); coordline(t[7], t[4]); coordline(t[0],t[4]); coordline(t[1],t[5]); coordline(t[2],t[6]); coordline(t[3],t[7]); // Flash time yet ? #define FLASH_FRAMES 4 if (frame > 128 - FLASH_FRAMES) { fade = frame - (128-FLASH_FRAMES); fade = -fade * (256/FLASH_FRAMES); } render_buf(); #ifdef ITEM_NUM normvideo(); writexy(1,1,"4: %d", frame); #endif update(); delay(framerate); } } // Draw a triangle in the buffer (it clips to the screen size!) #ifndef min #define min(a,b) ( ((a) < (b)) ? (a) : (b) ) #endif #ifndef max #define max(a,b) ( ((a) > (b)) ? (a) : (b) ) #endif int *tri_leftedge, *tri_rightedge; void draw_tri_edge(int a1, int b1, int a2, int b2) { int x1 = a1, y1 = b1, x2 = a2, y2 = b2, dx, y; // Make sure we're drawing from top to bottom if (y2 < y1) { int temp = y2; y2 = y1; y1 = temp; temp = x2; x2 = x1; x1 = temp; } // Breakouts if ((y2 < 0) || (y1 > CON_ROWS*2)) return; if (y1 == y2) { tri_leftedge[y1] = min(tri_leftedge[y1], x1); tri_rightedge[y1] = max(tri_rightedge[y1], x2); return; } // Deltas dx = ((x2-x1) << 8) / (y2-y1); x1 = x1 << 8; // Clip top to 0 if it's negative if (y1 < 0) { int i; for (i=y1; i<=0; i++) x1 += dx; y1 = 0; } // Clip bottom if necessary y2 = min(y2, CON_ROWS*2); // Scan edge for (y=y1; y> 8; tri_leftedge[y] = min(tri_leftedge[y], x); tri_rightedge[y] = max(tri_rightedge[y], x); x1 += dx; } } void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, unsigned char shade) { int y; // Clear edge buffers for (y=0; y 0) return; // flipped normal unsigned char shade = (unsigned char)(vect_dot(pshade, light) * 255); // split into two triangles coordtri(p1, p2, p3, shade); coordtri(p3, p4, p1, shade); } struct face4 { int p1, p2, p3, p4; }; struct face4info { val z; vect normal; }; __inline unsigned char lookup(int x, int y) { int c = (int)(buf[ (y)*(CON_COLS*2) + (x) ]); if (fade) c += fade; return (c > 255) ? 255 : (unsigned char)(c); } void rendershapes(void) { int y; // Handle interrupt if (keypressed()) raise(SIGTERM); // Render to screen clrscr(); for (y=0; yz = (tp[f.p1][2] + tp[f.p2][2] + tp[f.p3][2] + tp[f.p4][2]) / 4.0; u[0] = tp[f.p2][0] - tp[f.p1][0]; u[1] = tp[f.p2][1] - tp[f.p1][1]; u[2] = tp[f.p2][2] - tp[f.p1][2]; v[0] = tp[f.p4][0] - tp[f.p1][0]; v[1] = tp[f.p4][1] - tp[f.p1][1]; v[2] = tp[f.p4][2] - tp[f.p1][2]; fi->normal[0] = u[1]*v[2] - u[2]*v[1]; fi->normal[1] = u[2]*v[0] - u[0]*v[2]; fi->normal[2] = u[0]*v[1] - u[1]*v[0]; vect_normal(fi->normal); } // 5: spinning shaded cube void item5(void) { int frame; vect cubepoints[8] = { {-1.0, 1.0,-1.0}, { 1.0, 1.0,-1.0}, { 1.0,-1.0,-1.0}, {-1.0,-1.0,-1.0}, {-1.0, 1.0, 1.0}, { 1.0, 1.0, 1.0}, { 1.0,-1.0, 1.0}, {-1.0,-1.0, 1.0} }; vect transpoints[8]; coord cubecoords[8]; struct face4info cubefaceinfo[6]; struct face4 cubeface[6] = { { 0,1,2,3 }, { 7,6,5,4 }, { 4,5,1,0 }, { 6,7,3,2 }, { 0,3,7,4 }, { 5,6,2,1 } }; for (frame=0; frame<256; frame++) { int i; // fade if (frame <= FLASH_FRAMES) fade = 256-frame*(256/FLASH_FRAMES); else if (frame > 256 - FLASH_FRAMES) fade = (frame - 256 + FLASH_FRAMES)*256/FLASH_FRAMES; else fade = 0; // Clear buffer memset(buf, 0, (CON_ROWS*2)*(CON_COLS*2)); // Transform #define trans(v) vect_rot_z(v, (val)frame / 64.0 * PI); \ vect_rot_y(v, (val)frame / 128.0 * PI); \ vect_rot_x(v, (val)frame / 128.0 * PI); \ v[2] += 3.0; for (i=0; i<8; i++) { vect_set(transpoints[i], cubepoints[i]); trans(transpoints[i]); make_coord(cubecoords[i], transpoints[i]); } #undef trans for (i=0; i<6; i++) calcface(&(cubefaceinfo[i]), cubeface[i], transpoints); // Draw drawworld( cubeface, cubefaceinfo, cubecoords, 6 ); rendershapes(); #ifdef ITEM_NUM normvideo(); writexy(1,1,"5: %d", frame); #endif update(); delay(framerate); } } // 6: fade to black void item6(void) { // Clear buffer memset(buf, 0, (CON_ROWS*2)*(CON_COLS*2)); for (fade=256; fade>=0; fade-=16) { rendershapes(); #ifdef ITEM_NUM normvideo(); writexy(1,1,"6: %d", fade); #endif update(); delay(framerate); } } void cleanup(int i) { free(buf); /* print copyright notice */ update(); donecons(); printf("Demo3D (C) Emil Mikulic, 1999-2002.\n\n"); exit(0); } void demo3d(void) { int i; /* figure out terminal size, aspect, etc. */ val xunit = (val)CON_COLS/4.0; val yunit = (val)CON_ROWS/3.0; val aspect = xunit/yunit; ysize = (val)CON_ROWS/2.0; xsize = ysize * aspect; xofs = (val)CON_COLS/2.0; /* allocate screen buffer */ buf = (char*)malloc(CON_COLS * CON_ROWS); for (i=0; i