/* core.c - Don Yang (uguu.org) 05/13/03 */ /*@ -compdef -realcompare -usedef @*/ #include"global.h" #include"bg.h" #include"chara.h" #include"core.h" /* State tables (see gen_ssa.pl) */ static const int TypeMap[SONG_LENGTH] = {1,1,1,2, 1,1,3,4,3,5, 1,1,1,2, 3,5,3,7,7,5, 1,1,3,4,3,5, 1,1,1,2, 6,7,8,5, 1,1,3,5, 1,1,1,2, 1,1,1,2, 0,0}; static const int VerseMap[SONG_LENGTH] = {0,0,0,0, 1,1,1,1,1,1, 2,2,2,2, 3,3,3,3,3,3, 4,4,4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 0,0}; /* Camera state */ int ManualClip = ClipNone; /* Program clock */ double CurrentTime; static double StartTime, LastTime0, LastTime1; static unsigned int FrameCount, LastFrameCount, FramesPerTick; static void GetCurrentState(/*@out@*/int *verse, /*@out@*/int *next, /*@out@*/int *linetype, /*@out@*/double *position); static void SetGlobalLight(void); static void UpdateClock(void); /***************************************************************** Cleanup */ void Cleanup(void) { UninitChara(); /* (chara.c) */ UninitBG(); /* (bg.c) */ } /* Cleanup() */ /******************************************************************** Init */ void Init(void) { struct timeb t; /* Reset clock */ (void)ftime(&t); StartTime = LastTime0 = LastTime1 = (double)t.time + (t.millitm / 1000.0); CurrentTime = 0.0; FrameCount = LastFrameCount = 0; FramesPerTick = 1; srand((unsigned)t.time); InitBG(); /* (bg.c) */ } /* Init() */ /****************************************************************** Render */ void Render(int width, int height) { int verse, next, linetype, twidth; double position; glDrawBuffer(GL_BACK); glClear(GL_DEPTH_BUFFER_BIT); UpdateClock(); GetCurrentState(&verse, &next, &linetype, &position); /* Draw background (bg.c) */ if( verse == next || position < 0.75 ) { /* Stable background */ glViewport(0, 0, width, height); DrawBG(width, height, 0, width, verse); } else { /* Transition */ twidth = (int)((1.0 - position) * (width * 4)); if( twidth >= width ) { DrawBG(width, height, 0, width, verse); } else if( twidth <= 0 ) { DrawBG(width, height, 0, width, next); } else { DrawBG(width, height, 0, twidth, verse); DrawBG(width, height, twidth, width, next); } } /* Draw mascot (chara.c) */ SetViewport3D(width, height, 0, width, 0); DrawChara(linetype, position); } /* Render() */ /*********************************************************** SetViewport2D */ void SetViewport2D(int width, int height, int left, int right) { double a, lf, rf; assert(left >= 0 && right <= width && left < right); glViewport(left, 0, right - left, height); /* Parallel viewing volume */ lf = (double)(2 * left) / (double)width - 1.0; rf = (double)(2 * right) / (double)width - 1.0; glMatrixMode(GL_PROJECTION); glLoadIdentity(); if( width > height ) { a = (double)width / (double)height; gluOrtho2D(a*lf, a*rf, -1.0, 1.0); } else { a = (double)height / (double)width; gluOrtho2D(lf, rf, -a, a); } /* Rotating camera. Background should be drawn between (-SCENE_2D_XSIZE, -1) - (SCENE_2D_XSIZE, 1) */ glTranslated( fmod(CurrentTime, CAMERA_ROTATE_PERIOD) - CAMERA_ROTATE_PERIOD / 2.0, 0.0, 0.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Default states. Drawing should always return to this state. */ glShadeModel(GL_SMOOTH); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glDisable(GL_FOG); glDisable(GL_LIGHTING); glDisable(GL_NORMALIZE); glDisable(GL_POLYGON_OFFSET_FILL); } /* SetViewport2D() */ /*********************************************************** SetViewport3D */ void SetViewport3D(int width, int height, int left, int right, int fog) { static const GLfloat fogcolor[2][4] = { {1.0f, 1.0f, 1.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f} }; static const double x = 1.0; double a, lf, rf; assert(left >= 0 && right <= width && left < right); assert(fog >= 0 && fog <= 2); glViewport(left, 0, right - left, height); /* Perspective viewing volume */ lf = (double)(2 * left) / (double)width - 1.0; rf = (double)(2 * right) / (double)width - 1.0; glMatrixMode(GL_PROJECTION); glLoadIdentity(); if( width > height ) { a = x * (double)width / (double)height; glFrustum(a*lf, a*rf, -x, x, 1.0, 1000.0); } else { a = x * (double)height / (double)width; glFrustum(x*lf, x*rf, -a, a, 1.0, 1000.0); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* Rotating camera. Objects should be drawn between (-SCENE_3D_XSIZE, -SCENE_3D_ZSIZE) - (SCENE_3D_XSIZE, SCENE_3D_ZSIZE) */ a = fmod(CurrentTime + 10.0, CAMERA_ROTATE_PERIOD) / (CAMERA_ROTATE_PERIOD * 0.5); lf = sin(a * PI); rf = cos(a * PI); gluLookAt(-5.0*x*lf, 2.7*x, -5.0*x*rf, 5.0*x*lf, 1.9*x, 5.0*x*rf, 0.0, 1.0, 0.0); ManualClip = (a < 0.15 || a > 1.85) ? ClipNegativeZ : (a > 0.85 && a < 1.15) ? ClipPositiveZ : ClipNone; /* Fog */ if( fog > 0 ) { glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, (GLfloat)(SCENE_3D_ZSIZE * 0.4)); glFogf(GL_FOG_END, (GLfloat)(SCENE_3D_ZSIZE * 0.9)); glFogfv(GL_FOG_COLOR, fogcolor[fog - 1]); } else { glDisable(GL_FOG); } /* Lights and default states. Drawing should always return to default state. */ SetGlobalLight(); } /* SetViewport3D() */ /********************************************************* GetCurrentState */ static void GetCurrentState(/*@out@*/int *verse, /*@out@*/int *next, /*@out@*/int *linetype, /*@out@*/double *position) { double songtime; int line; /* The state machine is entirely time-based. Pausing animation should be possible by simply freezing the clock. Note that reversing animation is not possible, because most routines rely on time moving in positive direction. */ songtime = fmod(CurrentTime, (double)SONG_LENGTH); line = (int)songtime; *verse = VerseMap[line]; *next = VerseMap[(line + 1) % SONG_LENGTH]; *linetype = TypeMap[line]; *position = fmod(songtime, 1.0); } /* GetCurrentState() */ /********************************************************** SetGlobalLight */ static void SetGlobalLight(void) { static GLfloat position[4] = {100.0f, 110.0f, 100.0f, 0.0f}; static GLfloat direction[4] = {-1.0f, -1.1f, -1.0f, 0.0f}; static GLfloat ambient[4] = {0.0f, 0.0f, 0.0f, 1.0f}; static GLfloat diffuse[4] = {1.0f, 1.0f, 1.0f, 1.0f}; static GLfloat specular[4] = {1.0f, 1.0f, 1.0f, 1.0f}; static GLfloat globalambient[4] = {0.0f, 0.0f, 0.0f, 1.0f}; glShadeModel(GL_SMOOTH); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_NORMALIZE); glDisable(GL_BLEND); glDisable(GL_POLYGON_OFFSET_FILL); glCullFace(GL_BACK); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, globalambient); glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, specular); glLightfv(GL_LIGHT0, GL_POSITION, position); glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, direction); } /* SetGlobalLight() */ /************************************************************* UpdateClock */ static void UpdateClock(void) { #ifndef NDEBUG static unsigned int lasttime = 0, lastfps = 0; char dbgtext[256]; #endif struct timeb t; double current; /* Get current time */ (void)ftime(&t); current = (double)t.time + (t.millitm / 1000.0); /* Display frame rate. This uses real time and not corrected time. */ #ifndef NDEBUG if( (unsigned long)t.time != lasttime ) { if( lasttime != 0 ) { sprintf(dbgtext, "time=%g, fps=%u\n", current - StartTime, FrameCount - lastfps); #ifdef WIN32_GUI OutputDebugStringA(dbgtext); #else (void)fputs(dbgtext, stdout); #endif } lasttime = (unsigned int)t.time; lastfps = FrameCount; } #endif /* Compensate for low resolution clocks. Because of the compensation scheme, this function should only be called once per frame. */ if( current == LastTime0 ) { /* Same time observed twice, interpolate with frame counter. Ideally, we need to keep a ring buffer of frame times, but estimating it with only one time is a lot faster. */ if( LastTime0 == LastTime1 ) { current += 0.02 * (FrameCount - LastFrameCount); } else { current += (LastTime0 - LastTime1) * (FrameCount - LastFrameCount) / (double)FramesPerTick; } } else { /* Different time from last, record ticks */ LastTime1 = LastTime0; LastTime0 = current; FramesPerTick = FrameCount - LastFrameCount + 1; LastFrameCount = FrameCount; } CurrentTime = (current - StartTime) / LINE_LENGTH; FrameCount++; } /* UpdateClock() */