/* koyomi0.c - Don Yang (uguu.org) 09/06/09 */ /*@ -realcompare @*/ #include #include #include #include #include #include #include #define PI 3.14159265358979323846264338327950288419716939937510 #define WINDOW_TITLE "Koyomi" /* Object parameters */ #define OBJECT_STEPS 48 #define OBJECT_COUNT 7 #define MIN_X -100 #define MAX_X 100 #define MIN_Y MIN_X #define MAX_Y MAX_X #define MIN_Z MIN_X #define MAX_Z MAX_X * 3 /* Texture parameters */ #define TEXTURE_SIZE 512 #define TEXTURE_GRADIENT_SCALE 1.0 #define CLOUD_DIVISION_COUNT 10 #define CLOUD_SIZE (1 << CLOUD_DIVISION_COUNT) #define CLOUD_SPIKE_FACTOR 3 #define CLOUD_SPIKE_STEP (CLOUD_SIZE / (1 << CLOUD_SPIKE_FACTOR)) #define CLOUD_SPIKE_COUNT (4 << CLOUD_SPIKE_FACTOR) #define CLOUD_INIT_RANGE 0.2 #define CLOUD_INIT_OFFSET 0.1 #define CLOUD_ROUGHNESS 0.9 #define RandomNumber() (((double)rand()) / (double)RAND_MAX) /* Global clock, updated exactly once per frame */ static double CurrentTime; /* Stats counters */ static double StartTime; static int CurrentFrame; /* Precompiled object */ static GLuint TaraiObjectList; /* Object position */ typedef struct { struct { int face; /* target face in bounding box */ double p[6]; /* x, y, z, rotate x, rotate y, rotate z */ double t; /* time */ } key[3]; } TaraiPosition; static TaraiPosition TaraiObject[OBJECT_COUNT]; /* Texture image */ static GLuint TextureObject; static GLubyte TextureImage[TEXTURE_SIZE][TEXTURE_SIZE][3]; static void Init(void); static void InitObj(void); static void InitTexture(void); static void InitObjPositions(void); static void Render(void); static void Animate(void); static void Reshape(/*@unused@*/int w, /*@unused@*/int h); static void Quit(/*@unused@*/unsigned char c, /*@unused@*/int u, /*@unused@*/int v); static void UpdateClock(void); static void GenerateNewKeyframe(TaraiPosition *object); static void SetObjectPositions(void); static double ConvertRange(double input0, double input1, double output0, double output1, double x); int main(int argc, char **argv) { glutInit(&argc, argv); Init(); glutMainLoop(); return 0; } /* Initialize animation loop and objects */ static void Init(void) { /* Initialize OpenGL + GLUT */ glutInitDisplayMode((unsigned)(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)); glutSetWindow(glutCreateWindow(WINDOW_TITLE)); glutDisplayFunc(Render); glutIdleFunc(Animate); glutReshapeFunc(Reshape); glutKeyboardFunc(Quit); /* Set OpenGL options */ glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glShadeModel(GL_SMOOTH); /* Initialize objects */ UpdateClock(); srand((unsigned int)CurrentTime); InitObj(); InitTexture(); StartTime = CurrentTime; CurrentFrame = 0; InitObjPositions(); } /* Initialize geometry */ static void InitObj(void) { /* Contour curve (X, Y, normal X, normal Y). Bottom center of the tarai is at (0, 0). */ #define POINT_COUNT 9 static const int curve[POINT_COUNT][4] = { {23, -9, 0, -1}, {25, -8, 3, -1}, {30, 7, 3, -1}, {31, 7, 0, -1}, {32, 8, 1, 0}, {31, 9, 0, 1}, {29, 9, -3, 1}, {24, -6, -3, 1}, {23, -7, 0, 1} }; double a; int i, j; TaraiObjectList = glGenLists(1); assert(TaraiObjectList != 0); glNewList(TaraiObjectList, GL_COMPILE); /* Side rings */ for(i = 0; i < POINT_COUNT - 1; i++) { glBegin(GL_QUAD_STRIP); for(j = 0; j <= OBJECT_STEPS; j++) { a = (double)(j * 2) * PI / OBJECT_STEPS; glNormal3d((GLdouble)curve[i][2] * cos(a), (GLdouble)curve[i][3], (GLdouble)curve[i][2] * sin(a)); glVertex3d((GLdouble)curve[i][0] * cos(a), (GLdouble)curve[i][1], (GLdouble)curve[i][0] * sin(a)); glNormal3d((GLdouble)curve[i + 1][2] * cos(a), (GLdouble)curve[i + 1][3], (GLdouble)curve[i + 1][2] * sin(a)); glVertex3d((GLdouble)curve[i + 1][0] * cos(a), (GLdouble)curve[i + 1][1], (GLdouble)curve[i + 1][0] * sin(a)); } glEnd(); } /* Top cap */ glBegin(GL_POLYGON); glNormal3d(0.0, (GLdouble)curve[POINT_COUNT - 1][3], 0.0); for(i = 0; i < OBJECT_STEPS; i++) { a = (double)(i * -2) * PI / OBJECT_STEPS; glVertex3d((GLdouble)curve[POINT_COUNT - 1][0] * cos(a), (GLdouble)curve[POINT_COUNT - 1][1], (GLdouble)curve[POINT_COUNT - 1][0] * sin(a)); } glEnd(); /* Bottom cap */ glBegin(GL_POLYGON); glNormal3d(0.0, (GLdouble)curve[0][3], 0.0); for(i = 0; i < OBJECT_STEPS; i++) { a = (double)(i * 2) * PI / OBJECT_STEPS; glVertex3d((GLdouble)curve[0][0] * cos(a), (GLdouble)curve[0][1], (GLdouble)curve[0][0] * sin(a)); } glEnd(); glEndList(); #undef POINT_COUNT /* We could put in a glutSolidTeapot here just for kicks, but due to strange polygon culling, it actually starts to look weird if you stare at it long enough, so no teapot. */ } /* Position objects */ static void InitObjPositions(void) { int i; for(i = 0; i < OBJECT_COUNT; i++) { TaraiObject[i].key[2].face = 5; TaraiObject[i].key[2].t = CurrentTime; TaraiObject[i].key[2].p[0] = ConvertRange(0.0, 1.0, MIN_X, MAX_X, RandomNumber()); TaraiObject[i].key[2].p[1] = ConvertRange(0.0, 1.0, MIN_Y, MAX_Y, RandomNumber()); TaraiObject[i].key[2].p[2] = ConvertRange(0.0, 1.0, MIN_Z, MAX_Z, RandomNumber()); TaraiObject[i].key[2].p[3] = ConvertRange(0.0, 1.0, -360.0, 360.0, RandomNumber()); TaraiObject[i].key[2].p[4] = ConvertRange(0.0, 1.0, -360.0, 360.0, RandomNumber()); TaraiObject[i].key[2].p[5] = ConvertRange(0.0, 1.0, -360.0, 360.0, RandomNumber()); GenerateNewKeyframe(&(TaraiObject[i])); } } /* Animate and render a single frame */ static void Render(void) { int width, height, i; double dw, dh; UpdateClock(); glDrawBuffer(GL_BACK); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Set projection */ width = glutGet(GLUT_WINDOW_WIDTH); height = glutGet(GLUT_WINDOW_HEIGHT); if( width > height ) { dw = (double)width / (double)height; dh = 1.0; } else { dw = 1.0; dh = (double)height / (double)width; } glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-dw, dw, -dh, dh, 30.0, 1e6); /* Notice that the up vector of the camera points downwards. This makes the Y coordinates consistent with how we generated the environment map, with smaller Y values near the top of the screen rather than the bottom. Alternatively, we could just generate our environment map upside down, but that makes it cumbersome to debug, hence this tweak. */ gluLookAt(0.0, 0.0, -14.0 * (MAX_X - MIN_X), (MIN_X+MAX_X) / 2.0, (MIN_Y+MAX_Y) / 2.0, (MIN_Z+MAX_Z) / 2.0, 0.0, -1.0, 0.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Draw objects */ SetObjectPositions(); for(i = 0; i < OBJECT_COUNT; i++) { glPushMatrix(); glTranslated(TaraiObject[i].key[2].p[0], TaraiObject[i].key[2].p[1], TaraiObject[i].key[2].p[2]); glRotated(TaraiObject[i].key[2].p[3], 1.0, 0.0, 0.0); glRotated(TaraiObject[i].key[2].p[4], 0.0, 1.0, 0.0); glRotated(TaraiObject[i].key[2].p[5], 0.0, 0.0, 1.0); glCallList(TaraiObjectList); glPopMatrix(); } glutSwapBuffers(); glFlush(); } /* Force redraw on clock */ static void Animate(void) { glutPostRedisplay(); } /* Force redraw on reshape */ static void Reshape(/*@unused@*/int w, /*@unused@*/int h) { glutPostRedisplay(); } /* Exit */ static void Quit(/*@unused@*/unsigned char c, /*@unused@*/int u, /*@unused@*/int v) { glFlush(); if( CurrentTime - StartTime > 1.0 ) printf("fps = %f\n", CurrentFrame / (CurrentTime - StartTime)); exit(EXIT_SUCCESS); } /* Update animation time */ static void UpdateClock(void) { struct timeval t; int gettimeofday_status = gettimeofday(&t, NULL); assert(gettimeofday_status == 0); CurrentTime = (double)t.tv_sec + (double)(t.tv_usec / 1000000.0); CurrentFrame++; } /* Generate new keyframe for object */ static void GenerateNewKeyframe(TaraiPosition *object) { /* Replace target keyframe with current position. This makes the transitions smoother. */ memcpy(&(object->key[1]), &(object->key[2]), sizeof(object->key[0])); /* Replace old keyframe */ memcpy(&(object->key[0]), &(object->key[1]), sizeof(object->key[0])); /* Generate new keyframe, making sure that the object will bounce off of a different face. Note that we never bounce off the Z faces, because motion in that direction tend to look too subtle due to how our camera is placed. */ object->key[1].t += ConvertRange(0.0, 1.0, 2.0, 7.0, RandomNumber()); while( object->key[1].face == object->key[0].face ) object->key[1].face = rand() % 4; object->key[2].face = object->key[1].face; object->key[1].p[0] = ConvertRange(0.0, 1.0, MIN_X, MAX_X, RandomNumber()); object->key[1].p[1] = ConvertRange(0.0, 1.0, MIN_Y, MAX_Y, RandomNumber()); object->key[1].p[2] = ConvertRange(0.0, 1.0, MIN_Z, MAX_Z, RandomNumber()); object->key[1].p[3] = ConvertRange(0.0, 1.0, -360.0, 360.0, RandomNumber()); object->key[1].p[4] = ConvertRange(0.0, 1.0, -360.0, 360.0, RandomNumber()); object->key[1].p[5] = ConvertRange(0.0, 1.0, -360.0, 360.0, RandomNumber()); if( object->key[1].face == 0 ) { object->key[1].p[0] = MIN_X; } else if( object->key[1].face == 1 ) { object->key[1].p[0] = MAX_X; } else if( object->key[1].face == 2 ) { object->key[1].p[1] = MIN_Y; } else { object->key[1].p[1] = MAX_Y; } } /* Compute new positions for each object */ static void SetObjectPositions(void) { TaraiPosition *x; int i, j; for(i = 0; i < OBJECT_COUNT; i++) { x = &(TaraiObject[i]); for(j = 0; j < 6; j++) { x->key[2].p[j] = ConvertRange(x->key[0].t, x->key[1].t, x->key[0].p[j], x->key[1].p[j], CurrentTime); } x->key[2].t = CurrentTime; if( CurrentTime >= x->key[1].t ) GenerateNewKeyframe(x); } } /* Texture bits from generate_env_map3.c */ /* Tile image for cloud texture */ static double EnvCloud[CLOUD_SIZE + 1][CLOUD_SIZE + 1]; /* Update pixel in cloud image */ static void SetCloudPixel(int x, int y, int lock_edges, double value) { if( lock_edges == 0 || (x != 0 && x != CLOUD_SIZE && y != 0 && y != CLOUD_SIZE) ) { EnvCloud[y][x] = value; } } /* Subdivide & interpolate cloud image recursively */ static void SubDivideCloud(int step, double roughness, int lock_edges) { int x, y, cx, cy, nx, ny; for(; step > 1; step /= 2) { for(y = 0; y < CLOUD_SIZE; y = ny) { cy = y + step / 2; ny = y + step; for(x = 0; x < CLOUD_SIZE; x = nx) { cx = x + step / 2; nx = x + step; /* Add center point */ SetCloudPixel(cx, cy, lock_edges, (EnvCloud[y][x] + EnvCloud[y][nx] + EnvCloud[ny][x] + EnvCloud[ny][nx]) / 4.0); /* Add edge points */ SetCloudPixel(cx, y, lock_edges, (EnvCloud[y][x] + EnvCloud[y][nx] + EnvCloud[cy][cx]) / 3.0); SetCloudPixel(x, cy, lock_edges, (EnvCloud[y][x] + EnvCloud[cy][cx] + EnvCloud[ny][x]) / 3.0); SetCloudPixel(nx, cy, lock_edges, (EnvCloud[y][nx] + EnvCloud[cy][cx] + EnvCloud[ny][nx]) / 3.0); SetCloudPixel(cx, ny, lock_edges, (EnvCloud[cy][cx] + EnvCloud[ny][x] + EnvCloud[ny][nx]) / 3.0); /* Display newly added points */ SetCloudPixel(cx, y, lock_edges, EnvCloud[y][cx] + (RandomNumber() - 0.5) * roughness); SetCloudPixel(x, cy, lock_edges, EnvCloud[cy][x] + (RandomNumber() - 0.5) * roughness); SetCloudPixel(cx, cy, lock_edges, EnvCloud[cy][cx] + (RandomNumber() - 0.5) * roughness); SetCloudPixel(nx, cy, lock_edges, EnvCloud[cy][nx] + (RandomNumber() - 0.5) * roughness); SetCloudPixel(cx, ny, lock_edges, EnvCloud[ny][cx] + (RandomNumber() - 0.5) * roughness); } } roughness *= 0.5; } } /* Generate cloud tile image */ static void GenerateCloudImage(void) { int i, x, y; /* First pass: generate base image */ for(y = 0; y <= CLOUD_SIZE; y += CLOUD_SPIKE_STEP) { for(x = 0; x <= CLOUD_SIZE; x += CLOUD_SPIKE_STEP) EnvCloud[y][x] = 0.0; } for(i = 0; i < CLOUD_SPIKE_COUNT; i++) { x = (int)(RandomNumber() * CLOUD_SIZE / CLOUD_SPIKE_STEP) * CLOUD_SPIKE_STEP; y = (int)(RandomNumber() * CLOUD_SIZE / CLOUD_SPIKE_STEP) * CLOUD_SPIKE_STEP; EnvCloud[y][x] = CLOUD_INIT_OFFSET + CLOUD_INIT_RANGE * RandomNumber(); } SubDivideCloud(CLOUD_SPIKE_STEP, CLOUD_ROUGHNESS / (1 << CLOUD_SPIKE_FACTOR), 0); /* Second pass: tile image */ for(i = 0; i <= CLOUD_SIZE; i++) { EnvCloud[CLOUD_SIZE][i] = EnvCloud[0][i]; EnvCloud[i][CLOUD_SIZE] = EnvCloud[i][0]; } SubDivideCloud(CLOUD_SIZE, CLOUD_ROUGHNESS, 1); /* Third pass: scale image */ for(y = 0; y < CLOUD_SIZE; y++) { for(x = 0; x < CLOUD_SIZE; x++) EnvCloud[y][x] *= 255.0; } } /* Convert value from one range to another */ static double ConvertRange(double input0, double input1, double output0, double output1, double x) { return ((output1 - output0) * (x - input0) / (input1 - input0)) + output0; } /* Convert value from one range to another, keeping only the remainder for out of bound values. */ static int ModulusRange(double input0, double input1, int output, double x) { int sx = (int)(output * (x - input0) / (input1 - input0)); if( sx < 0 ) return output - ((-sx) % output); return sx % output; } /* Set pixel from a gradient of 2 colors */ static void SetGradient(const double *color0, const double *color1, double value, unsigned char *pixels) { int i; for(i = 0; i < 3; i++) { pixels[i] = (unsigned char)ConvertRange(0.0, 1.0, color0[i], color1[i], value); } } /* Set pixel color based on gradient position in [-1, 1] */ static void SetTexturePixel(double y, unsigned char *pixels) { static const double top[3] = {107.0, 192.0, 229.0}; static const double middle[3] = {240.0, 250.0, 250.0}; static const double bottom[3] = {0.0, 128.0, 64.0}; static const double t1 = 0.40; static const double t2 = 0.39; int i; if( y > t1 ) { SetGradient(middle, top, ConvertRange(t1, 1.0, 0.0, 1.0, y), pixels); } else if( y > t2 ) { for(i = 0; i < 3; i++) pixels[i] = (unsigned char)middle[i]; } else { SetGradient(bottom, middle, ConvertRange(-1.0, t2, 0.0, 1.0, y), pixels); } } /* Generate gradient based texture image, then modulate top with cloud image */ static void GenerateTextureMap(void) { double dx, dy, r2, ly, tx, ty, p; int x, y, i, m; for(y = 0; y < TEXTURE_SIZE; y++) { dy = ConvertRange(0.0, (double)(TEXTURE_SIZE-1), 1.0, -1.0, (double)y); for(x = 0; x < TEXTURE_SIZE; x++) { dx = ConvertRange(0.0, (double)(TEXTURE_SIZE-1), -1.0, 1.0, (double)x); r2 = dx * dx + dy * dy; ly = TEXTURE_GRADIENT_SCALE * sqrt((1 - dx * dx) / (1 + TEXTURE_GRADIENT_SCALE * TEXTURE_GRADIENT_SCALE)); if( dy >= ly ) { /* Sky */ SetTexturePixel(1.0, TextureImage[y][x]); } else if( dy <= -ly ) { /* Ground */ SetTexturePixel(-1.0, TextureImage[y][x]); } else { /* Bits in between */ if( r2 < 1.0 ) { ty = dy / (TEXTURE_GRADIENT_SCALE * sqrt(1 - r2)); if( ty >= -1.0 && ty <= 1.0 ) SetTexturePixel(ty, TextureImage[y][x]); } } /* Modulate in cloud texture if we are on the sphere */ if( r2 < 1.0 ) { tx = dx / sqrt(1 - r2); ty = dy / sqrt(1 - r2); p = EnvCloud[ModulusRange(-1.0, 1.0, CLOUD_SIZE, tx)] [ModulusRange(-1.0, 1.0, CLOUD_SIZE, ty)]; if( p > 0.0 ) { for(i = 0; i < 3; i++) { m = (int)TextureImage[y][x][i] + (int)p; if( m > 255 ) TextureImage[y][x][i] = (unsigned char)255; else TextureImage[y][x][i] = (unsigned char)m; } } } } } } /* Initialize texture and bind it */ static void InitTexture(void) { GenerateCloudImage(); GenerateTextureMap(); glEnable(GL_TEXTURE_2D); glGenTextures(1, &TextureObject); glBindTexture(GL_TEXTURE_2D, TextureObject); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEXTURE_SIZE, TEXTURE_SIZE, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); }