/* koyomi6.c - Don Yang (uguu.org) 09/29/09 */ #include #include #include #include #include #define OBJECT_COUNT 7 /* Global clock, updated exactly once per frame */ double CurrentTime; /* Stats counters */ double StartTime; int CurrentFrame; /* Precompiled object */ GLuint TaraiObjectList; /* second dimension: keyframe0, keyframe1, current frame third dimension: x, y, z, rotate x, rotate y, rotate z, time, face */ double TaraiObject[OBJECT_COUNT][3][8]; /* Texture image */ int TextureObject; unsigned char TextureImage[512][512][3]; /* Tile image for cloud texture */ double EnvCloud[1024 + 1][1024 + 1]; /* Previously local variables */ int i, j, x, y, cx, cy, nx, ny; double dx, dy, r2, ly, ty, p; /* Gradient data */ double top[3] = {107, 192, 229}; double middle[3] = {240, 250, 250}; double bottom[3] = {0, 128, 64}; double t1 = 0.40; double t2 = 0.39; /* Contour curve (X, Y, normal X, normal Y). Bottom center of the tarai is at (0, 0). */ int curve[9][4] = { {69, -27, 0, -1}, {75, -24, 3, -1}, {90, 21, 3, -1}, {93, 21, 0, -1}, {96, 24, 1, 0}, {93, 27, 0, 1}, {87, 27, -3, 1}, {72, -18, -3, 1}, {69, -21, 0, 1} }; /* Convert value from one range to another */ double ConvertRange(double input0, double input1, double output0, double output1, double value) { return ((output1 - output0) * (value - input0) / (input1 - input0)) + output0; } /* Convert value from one range to another, keeping only the remainder for out of bound values. */ int ModulusRange(double value) { i = (int)(1024 * (value + 1) / 2); if( i < 0 ) return 1024 - ((-i) % 1024); return i % 1024; } /* Generate a random number for a particular range */ double RandomNumber(double output0, double output1) { return ConvertRange(0, RAND_MAX, output0, output1, rand()); } /* Update pixel in cloud image */ void SetCloudPixel(int sx, int sy, double value) { if( j == 0 || (sx != 0 && sx != 1024 && sy != 0 && sy != 1024) ) EnvCloud[sy][sx] = value; } /* Subdivide & interpolate cloud image recursively */ void SubDivideCloud(int step, double roughness) { for(; step > 1; step /= 2) { for(y = 0; y < 1024; y = ny) { cy = y + step / 2; ny = y + step; for(x = 0; x < 1024; x = nx) { cx = x + step / 2; nx = x + step; /* Add center point */ SetCloudPixel(cx, cy, (EnvCloud[y][x] + EnvCloud[y][nx] + EnvCloud[ny][x] + EnvCloud[ny][nx]) / 4); /* Add edge points */ SetCloudPixel(cx, y, (EnvCloud[y][x] + EnvCloud[y][nx] + EnvCloud[cy][cx]) / 3); SetCloudPixel(x, cy, (EnvCloud[y][x] + EnvCloud[cy][cx] + EnvCloud[ny][x]) / 3); SetCloudPixel(nx, cy, (EnvCloud[y][nx] + EnvCloud[cy][cx] + EnvCloud[ny][nx]) / 3); SetCloudPixel(cx, ny, (EnvCloud[cy][cx] + EnvCloud[ny][x] + EnvCloud[ny][nx]) / 3); /* Display newly added points */ SetCloudPixel(cx, y, EnvCloud[y][cx] + RandomNumber(-0.5, 0.5) * roughness); SetCloudPixel(x, cy, EnvCloud[cy][x] + RandomNumber(-0.5, 0.5) * roughness); SetCloudPixel(cx, cy, EnvCloud[cy][cx] + RandomNumber(-0.5, 0.5) * roughness); SetCloudPixel(nx, cy, EnvCloud[cy][nx] + RandomNumber(-0.5, 0.5) * roughness); SetCloudPixel(cx, ny, EnvCloud[ny][cx] + RandomNumber(-0.5, 0.5) * roughness); } } roughness /= 2; } } /* Set pixel from a gradient of 2 colors */ void SetGradient(double *color0, double *color1, double value) { for(i = 0; i < 3; i++) { TextureImage[y][x][i] = (unsigned char)ConvertRange(0, 1, color0[i], color1[i], value); } } /* Set pixel color based on gradient position in [-1, 1] */ void SetTexturePixel(void) { if( ty > t1 ) { SetGradient(middle, top, ConvertRange(t1, 1, 0, 1, ty)); } else if( ty > t2 ) { for(i = 0; i < 3; i++) TextureImage[y][x][i] = (unsigned char)middle[i]; } else { SetGradient(bottom, middle, ConvertRange(-1, t2, 0, 1, ty)); } } /* Generate new keyframe for object */ void GenerateNewKeyframe(void) { /* Replace keyframes */ for(j = 0; j < 8; j++) TaraiObject[i][0][j] = TaraiObject[i][1][j] = TaraiObject[i][2][j]; /* Generate new keyframe, making sure that the object will bounce off of a different face. Note that we prefer to not bounce off the Z faces, because motion in that direction tend to look too subtle due to how our camera is placed. */ TaraiObject[i][1][6] += RandomNumber(2, 7); while( TaraiObject[i][2][7] == TaraiObject[i][0][7] ) TaraiObject[i][2][7] = floor(RandomNumber(0, 4)); for(j = 0; j < 6; j++) TaraiObject[i][1][j] = RandomNumber(-360, 360); if( TaraiObject[i][2][7] < 1 ) { TaraiObject[i][1][0] = -360; } else if( TaraiObject[i][2][7] < 2 ) { TaraiObject[i][1][0] = 360; } else if( TaraiObject[i][2][7] < 3 ) { TaraiObject[i][1][1] = -360; } else if( TaraiObject[i][2][7] < 4 ) { TaraiObject[i][1][1] = 360; } else { TaraiObject[i][1][2] = 360; } } /* Update animation time */ void UpdateClock(void) { struct timeval t; gettimeofday(&t, NULL); CurrentTime = t.tv_sec + t.tv_usec / 1e6; CurrentFrame++; } /* Compute new positions for each object */ void SetObjectPositions(void) { for(i = 0; i < OBJECT_COUNT; i++) { for(j = 0; j < 6; j++) { TaraiObject[i][2][j] = ConvertRange(TaraiObject[i][0][6], TaraiObject[i][1][6], TaraiObject[i][0][j], TaraiObject[i][1][j], CurrentTime); } TaraiObject[i][2][6] = CurrentTime; if( CurrentTime >= TaraiObject[i][1][6] ) GenerateNewKeyframe(); } } /* Animate and render a single frame */ void Render(void) { UpdateClock(); glDrawBuffer(GL_BACK); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Set projection */ x = glutGet(GLUT_WINDOW_WIDTH); y = glutGet(GLUT_WINDOW_HEIGHT); if( x > y ) { dx = x / (double)y; dy = 1; } else { dx = 1; dy = y / (double)x; } glViewport(0, 0, x, y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-dx, dx, -dy, dy, 30, 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, -14 * 720, 0, 0, 0, 0, -1, 0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Draw objects */ SetObjectPositions(); for(i = 0; i < OBJECT_COUNT; i++) { glPushMatrix(); glTranslated(TaraiObject[i][2][0], TaraiObject[i][2][1], TaraiObject[i][2][2]); glRotated(TaraiObject[i][2][3], 1, 0, 0); glRotated(TaraiObject[i][2][4], 0, 1, 0); glRotated(TaraiObject[i][2][5], 0, 0, 1); glCallList(TaraiObjectList); glPopMatrix(); } glutSwapBuffers(); glFlush(); } /* Force redraw on clock */ void Animate(void) { glutPostRedisplay(); } /* Force redraw on reshape */ void Reshape(int w, int h) { glutPostRedisplay(); } /* Exit */ void Quit(unsigned char c, int u, int v) { glFlush(); if( CurrentTime - StartTime > 1 ) printf("fps = %f\n", CurrentFrame / (CurrentTime - StartTime)); exit(EXIT_SUCCESS); } int main(int argc, char **argv) { glutInit(&argc, argv); /** Init(); **/ /* Initialize OpenGL + GLUT */ glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); glutSetWindow(glutCreateWindow("Koyomi")); 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(); **/ TaraiObjectList = glGenLists(1); glNewList(TaraiObjectList, GL_COMPILE); dx = atan2(0, -1); /* Side rings */ for(i = 0; i < 9 - 1; i++) { glBegin(GL_QUAD_STRIP); for(j = 0; j <= 48; j++) { p = (double)(j * 2) * dx / 48; glNormal3d((GLdouble)curve[i][2] * cos(p), (GLdouble)curve[i][3], (GLdouble)curve[i][2] * sin(p)); glVertex3d((GLdouble)curve[i][0] * cos(p), (GLdouble)curve[i][1], (GLdouble)curve[i][0] * sin(p)); glNormal3d((GLdouble)curve[i + 1][2] * cos(p), (GLdouble)curve[i + 1][3], (GLdouble)curve[i + 1][2] * sin(p)); glVertex3d((GLdouble)curve[i + 1][0] * cos(p), (GLdouble)curve[i + 1][1], (GLdouble)curve[i + 1][0] * sin(p)); } glEnd(); } /* Top cap */ glBegin(GL_POLYGON); glNormal3d(0, (GLdouble)curve[9 - 1][3], 0); for(i = 0; i < 48; i++) { p = (double)(i * -2) * dx / 48; glVertex3d((GLdouble)curve[9 - 1][0] * cos(p), (GLdouble)curve[9 - 1][1], (GLdouble)curve[9 - 1][0] * sin(p)); } glEnd(); /* Bottom cap */ glBegin(GL_POLYGON); glNormal3d(0, (GLdouble)curve[0][3], 0); for(i = 0; i < 48; i++) { p = (double)(i * 2) * dx / 48; glVertex3d((GLdouble)curve[0][0] * cos(p), (GLdouble)curve[0][1], (GLdouble)curve[0][0] * sin(p)); } glEnd(); glEndList(); /* 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. */ /** InitTexture(); **/ /** GenerateCloudImage(); **/ /* First pass: generate base image */ for(y = 0; y <= 1024; y += 128) { for(x = 0; x <= 1024; x += 128) EnvCloud[y][x] = 0; } for(i = 0; i < 32; i++) { x = ((int)RandomNumber(0, (double)1024 / 128)) * 128; y = ((int)RandomNumber(0, (double)1024 / 128)) * 128; EnvCloud[y][x] = RandomNumber(0.1, 0.1 + 0.2); } j = 0; SubDivideCloud(128, 0.9 / (1 << 3)); /* Second pass: tile image */ for(i = 0; i <= 1024; i++) { EnvCloud[1024][i] = EnvCloud[0][i]; EnvCloud[i][1024] = EnvCloud[i][0]; } j = 1; SubDivideCloud(1024, 0.9); /* Third pass: scale image */ for(y = 0; y < 1024; y++) { for(x = 0; x < 1024; x++) EnvCloud[y][x] *= 255; } /** GenerateTextureMap(); **/ for(y = 0; y < 512; y++) { dy = ConvertRange(0, 512-1, 1, -1, y); for(x = 0; x < 512; x++) { dx = ConvertRange(0, 512-1, -1, 1, x); r2 = dx * dx + dy * dy; ly = sqrt((1 - dx * dx) / 2); if( dy >= ly ) { /* Sky */ ty = 1; SetTexturePixel(); } else if( dy <= -ly ) { /* Ground */ ty = -1; SetTexturePixel(); } else { /* Bits in between */ if( r2 < 1 ) { ty = dy / sqrt(1 - r2); if( ty >= -1 && ty <= 1 ) SetTexturePixel(); } } /* Modulate in cloud texture if we are on the sphere */ if( r2 < 1 ) { p = EnvCloud[ModulusRange(dx / sqrt(1 - r2))] [ModulusRange(dy / sqrt(1 - r2))]; if( p > 0 ) { for(i = 0; i < 3; i++) { j = (int)TextureImage[y][x][i] + (int)p; if( j > 255 ) TextureImage[y][x][i] = (unsigned char)255; else TextureImage[y][x][i] = (unsigned char)j; } } } } } glEnable(GL_TEXTURE_2D); glGenTextures(1, &TextureObject); glBindTexture(GL_TEXTURE_2D, TextureObject); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 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); StartTime = CurrentTime; CurrentFrame = 0; /** InitObjPositions(); **/ for(i = 0; i < OBJECT_COUNT; i++) { for(j = 0; j < 6; j++) TaraiObject[i][2][j] = RandomNumber(-360, 360); TaraiObject[i][2][6] = CurrentTime; TaraiObject[i][2][7] = 5; GenerateNewKeyframe(); } glutMainLoop(); return 0; }