#include #include "nebulae.h" #include "SDL_image.h" #include "lephisto.h" #include "log.h" #include "opengl.h" #include "lfile.h" #include "rng.h" #include "menu.h" #include "player.h" #include "pause.h" #include "perlin.h" #define NEBULAE_Z 16 /* Z plane. */ #define NEBULAE_PUFFS 32 /* Amount of puffs to generate. */ #define NEBULAE_PATH_BG "gen/nebu_bg_%dx%d_%02d.png" #define NEBULAE_PUFF_BUFFER 300 /* Nebulae buffer. */ /* Extern. */ extern double gui_xoff, gui_yoff; extern Vec2 shake_pos; /* The nebulae textures. */ static GLuint nebu_textures[NEBULAE_Z]; static int nebu_w = 0; static int nebu_h = 0; static int nebu_pw, nebu_ph; /* Information on rendering. */ static int cur_nebu[2] = { 0, 1 }; static unsigned int last_render = 0; /* Nebuale properties. */ static double nebu_view = 0.; static double nebu_dt = 0.; /* Puff textures. */ static glTexture* nebu_pufftexs[NEBULAE_PUFFS]; /* Puff handling. */ typedef struct NebulaePuff_ { double x, y; /* Position. */ double height; /* Height vs player. */ int tex; /* Texture. */ } NebulaePuff; static NebulaePuff* nebu_puffs = NULL; static int nebu_npuffs = 0; static int nebu_checkCompat(const char* file); static void nebu_loadTexture(SDL_Surface* sur, int w, int h, GLuint tex); static int nebu_generate(void); static void nebu_generatePuffs(void); static int saveNebulae(float* map, const uint32_t w, const uint32_t h, const char* file); static SDL_Surface* loadNebulae(const char* file); static SDL_Surface* nebu_surfaceFromNebulaeMap(float* map, const int w, const int h); /* Initialize the nebulae. */ int nebu_init(void) { int i; char nebu_file[PATH_MAX]; SDL_Surface* nebu_sur; int ret; /* Special code to regenerate the nebulae. */ if((nebu_w == -9) && (nebu_h == -9)) nebu_generate(); /* Set expected sizes. */ nebu_w = SCREEN_W; nebu_h = SCREEN_H; nebu_pw = gl_pot(nebu_w); nebu_ph = gl_pot(nebu_h); nebu_generatePuffs(); /* Load each, checking for compatibility and padding. */ glGenTextures(NEBULAE_Z, nebu_textures); for(i = 0; i < NEBULAE_Z; i++) { snprintf(nebu_file, PATH_MAX, NEBULAE_PATH_BG, nebu_w, nebu_h, i); if(nebu_checkCompat(nebu_file)) { /* Incompatable. */ LOG("No nebulae found, generating (this may take a while)."); /* So we generate and reload. */ ret = nebu_generate(); if(ret != 0) /* An error has happened - break recursivity. */ return ret; return nebu_init(); } /* Load the file. */ nebu_sur = loadNebulae(nebu_file); if((nebu_sur->w != nebu_w) || (nebu_sur->h != nebu_h)) WARN("Nebulae raw size doesn't match expected! (%dx%d instead of %dx%d)", nebu_sur->w, nebu_sur->h, nebu_pw, nebu_ph); /* Load the textures. */ nebu_loadTexture(nebu_sur, nebu_pw, nebu_ph, nebu_textures[i]); } DEBUG("Loaded %d Nebulae Layers", NEBULAE_Z); return 0; } /* Load sur into tex, check for expected size of w and h. */ static void nebu_loadTexture(SDL_Surface* sur, int w, int h, GLuint tex) { SDL_Surface* nebu_sur; nebu_sur = gl_prepareSurface(sur); if((w != 0) && (h != 0) && ((nebu_sur->w != w) || (nebu_sur->h != h))) { WARN("Nebulae size doesn't match expected! (%dx%d instead of %dx%d)", nebu_sur->w, nebu_sur->h, nebu_pw, nebu_ph); return; } /* Load the textures. */ glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); /* Store into opengl saving only alpha channel in video memory. */ SDL_LockSurface(nebu_sur); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, nebu_sur->w, nebu_sur->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nebu_sur->pixels); SDL_UnlockSurface(nebu_sur); SDL_FreeSurface(nebu_sur); gl_checkErr(); } /* Clean up the nebu subsystem. */ void nebu_exit(void) { int i; glDeleteTextures(NEBULAE_Z, nebu_textures); for(i = 0; i < NEBULAE_PUFFS; i++) gl_freeTexture(nebu_pufftexs[i]); } /* Render the nebulae. */ void nebu_render(const double dt) { unsigned int t; double ndt; double tw, th; GLfloat col[4]; int tmp; /* Calculate frame to draw. */ t = SDL_GetTicks(); ndt = (t - last_render) / 1000.; if(ndt > nebu_dt) { /* Time to change. */ tmp = cur_nebu[0]; cur_nebu[0] += cur_nebu[0] - cur_nebu[1]; cur_nebu[1] = tmp; if(cur_nebu[0]+1 > NEBULAE_Z) cur_nebu[0] = NEBULAE_Z - 2; else if(cur_nebu[0] < 0) cur_nebu[0] = 1; last_render = t; ndt = 0.; } /* Set colour. */ col[0] = cPurple.r; col[1] = cPurple.g; col[2] = cPurple.b; col[3] = ndt / nebu_dt; tw = (double)nebu_w / (double)nebu_pw; th = (double)nebu_h / (double)nebu_ph; /* Set up the targets. */ /* Texture 0. */ glActiveTexture(GL_TEXTURE0); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, nebu_textures[cur_nebu[1]]); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); /* Texture 1. */ glActiveTexture(GL_TEXTURE1); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, nebu_textures[cur_nebu[0]]); /* Prepare it. */ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_INTERPOLATE); /* Colour. */ glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, col); /* Arguments. */ /* Arg0 */ glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_CONSTANT); glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); /* Arg 1. */ glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA); /* Arg 2. */ glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_ALPHA, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); /* Compensate possible rubmle. */ if(!paused) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glTranslated(shake_pos.x, shake_pos.y, 0.); } /* Now render. */ glBegin(GL_QUADS); glMultiTexCoord2d(GL_TEXTURE0, 0., 0.); glMultiTexCoord2d(GL_TEXTURE1, 0., 0.); glVertex2d(-SCREEN_W/2., -SCREEN_H/2.); glMultiTexCoord2d(GL_TEXTURE0, tw, 0.); glMultiTexCoord2d(GL_TEXTURE1, tw, 0.); glVertex2d(SCREEN_W/2., -SCREEN_H/2.); glMultiTexCoord2d(GL_TEXTURE0, tw, th); glMultiTexCoord2d(GL_TEXTURE1, tw, th); glVertex2d(SCREEN_W/2., SCREEN_H/2.); glMultiTexCoord2d(GL_TEXTURE0, 0., th); glMultiTexCoord2d(GL_TEXTURE1, 0., th); glVertex2d(-SCREEN_W/2., SCREEN_H/2.); glEnd(); if(!paused) glPopMatrix(); /* GL_PROJECTION */ /* Set values to defaults. */ glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glDisable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glDisable(GL_TEXTURE_2D); /* Did anything fail? */ gl_checkErr(); /* Now render the puffs. */ nebu_renderPuffs(dt, 1); } void nebu_renderOverlay(const double dt) { #define ANG45 0.70710678118654757 #define COS225 0.92387953251128674 #define SIN225 0.38268343236508978 /* Render the puffs. */ nebu_renderPuffs(dt, 0); /* Prepare the matrix. */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glTranslated(gui_xoff, gui_yoff, 0.); if(!paused) glTranslated(gui_xoff+shake_pos.x, gui_yoff+shake_pos.y, 0.); /* Mask for area player can still see (partially). */ glShadeModel(GL_SMOOTH); glBegin(GL_TRIANGLE_FAN); ACOLOUR(cPurple, 0.); glVertex2d(0., 0.); ACOLOUR(cPurple, 1.); glVertex2d(-nebu_view, 0.); glVertex2d(-nebu_view*COS225, nebu_view*SIN225); glVertex2d(-nebu_view*ANG45, nebu_view*ANG45); glVertex2d(-nebu_view*SIN225, nebu_view*COS225); glVertex2d( 0., nebu_view); glVertex2d( nebu_view*SIN225, nebu_view*COS225); glVertex2d( nebu_view*ANG45, nebu_view*ANG45); glVertex2d( nebu_view*COS225, nebu_view*SIN225); glVertex2d( nebu_view, 0.); glVertex2d( nebu_view*COS225, -nebu_view*SIN225); glVertex2d( nebu_view*ANG45, -nebu_view*ANG45); glVertex2d( nebu_view*SIN225, -nebu_view*COS225); glVertex2d( 0., -nebu_view); glVertex2d(-nebu_view*SIN225, -nebu_view*COS225); glVertex2d(-nebu_view*ANG45, -nebu_view*ANG45); glVertex2d(-nebu_view*COS225, -nebu_view*SIN225); glVertex2d(-nebu_view, 0.); glEnd(); /* GL_TRIANGLE_FAN */ /* Solid nebulae for areas the player can't see. */ glShadeModel(GL_FLAT); ACOLOUR(cPurple, 1.); glBegin(GL_TRIANGLE_FAN); /* Top left. */ glVertex2d(-SCREEN_W/2.-gui_xoff, SCREEN_H/2.-gui_yoff); glVertex2d(-nebu_view, 0.); glVertex2d(-nebu_view*COS225, nebu_view*SIN225); glVertex2d(-nebu_view*ANG45, nebu_view*ANG45); glVertex2d(-nebu_view*SIN225, nebu_view*COS225); glVertex2d(0., nebu_view); glVertex2d( SCREEN_W/2.-gui_xoff, SCREEN_H/2.-gui_yoff); glEnd(); /* GL_TRIANGLE_FAN */ glBegin(GL_TRIANGLE_FAN); /* Top right. */ glVertex2d( SCREEN_W/2.-gui_xoff, SCREEN_H/2.-gui_yoff); glVertex2d( 0., nebu_view); glVertex2d( nebu_view*SIN225, nebu_view*COS225); glVertex2d( nebu_view*ANG45, nebu_view*ANG45); glVertex2d( nebu_view*COS225, nebu_view*SIN225); glVertex2d( nebu_view, 0.); glVertex2d( SCREEN_W/2.-gui_xoff, -SCREEN_H/2.-gui_yoff); glEnd(); /* GL_TRIANGLE_FAN */ glBegin(GL_TRIANGLE_FAN); /* Bottom right. */ glVertex2d( SCREEN_W/2.-gui_xoff, -SCREEN_H/2.-gui_yoff); glVertex2d( nebu_view, 0.); glVertex2d( nebu_view*COS225, -nebu_view*SIN225); glVertex2d( nebu_view*ANG45, -nebu_view*ANG45); glVertex2d( nebu_view*SIN225, -nebu_view*COS225); glVertex2d( 0., -nebu_view); glVertex2d( -SCREEN_W/2.-gui_xoff, -SCREEN_H/2.-gui_yoff); glEnd(); /* GL_TRIANGLE_FAN */ glBegin(GL_TRIANGLE_FAN); /* Bottom left */ glVertex2d( -SCREEN_W/2.-gui_xoff, -SCREEN_H/2.-gui_yoff); glVertex2d( 0., -nebu_view); glVertex2d(-nebu_view*SIN225, -nebu_view*COS225); glVertex2d(-nebu_view*ANG45, -nebu_view*ANG45); glVertex2d(-nebu_view*COS225, -nebu_view*SIN225); glVertex2d(-nebu_view, 0.); glVertex2d(-SCREEN_W/2.-gui_xoff, SCREEN_H/2.-gui_yoff); glEnd(); /* GL_TRIANGLE_FAN */ glPopMatrix(); gl_checkErr(); #undef ANG45 #undef COS225 #undef SIN225 } /* Render the puffs. */ void nebu_renderPuffs(const double dt, int below_player) { int i; /* Main menu shouldn't have puffs. */ if(menu_isOpen(MENU_MAIN)) return; /* Seperate by layers. */ for(i = 0; i < nebu_npuffs; i++) { if((below_player && (nebu_puffs[i].height < 1.)) || (!below_player && (nebu_puffs[i].height > 1.))) { /* Calculate new position. */ if(!paused && (player != NULL)) { nebu_puffs[i].x -= player->solid->vel.x * nebu_puffs[i].height * dt; nebu_puffs[i].y -= player->solid->vel.y * nebu_puffs[i].height * dt; } /* Check boundaries. */ if(nebu_puffs[i].x > SCREEN_W + NEBULAE_PUFF_BUFFER) nebu_puffs[i].x = -NEBULAE_PUFF_BUFFER; else if(nebu_puffs[i].y > SCREEN_H + NEBULAE_PUFF_BUFFER) nebu_puffs[i].y = -NEBULAE_PUFF_BUFFER; else if(nebu_puffs[i].x < -NEBULAE_PUFF_BUFFER) nebu_puffs[i].x = SCREEN_W + NEBULAE_PUFF_BUFFER; else if(nebu_puffs[i].y < -NEBULAE_PUFF_BUFFER) nebu_puffs[i].y = SCREEN_H + NEBULAE_PUFF_BUFFER; /* Render. */ gl_blitStatic(nebu_pufftexs[nebu_puffs[i].tex], nebu_puffs[i].x, nebu_puffs[i].y, &cPurple); } } } /* Prepare the nebulae. */ void nebu_prep(double density, double volatility) { (void)volatility; int i; nebu_view = 1000. - density; /* At density 1000 you're blind. */ nebu_dt = 2000. / (density + 100.); /* Faster at higher density. */ nebu_npuffs = density/4.; nebu_puffs = realloc(nebu_puffs, sizeof(NebulaePuff)*nebu_npuffs); for(i = 0; i < nebu_npuffs; i++) { /* Position. */ nebu_puffs[i].x = (double)RNG(-NEBULAE_PUFF_BUFFER, SCREEN_W + NEBULAE_PUFF_BUFFER); nebu_puffs[i].y = (double)RNG(-NEBULAE_PUFF_BUFFER, SCREEN_H + NEBULAE_PUFF_BUFFER); /* Maybe make the size related. */ nebu_puffs[i].tex = RNG(0, NEBULAE_PUFFS-1); nebu_puffs[i].height = RNGF() + 0.2; } } /* Force generation of new nebulae. */ void nebu_forceGenerate(void) { nebu_w = nebu_h = -9; /* Magic numbers. ^.^ */ } /* Generate the nebulae. */ static int nebu_generate(void) { int i; float* nebu; char nebu_file[PATH_MAX]; int w, h; int ret; w = SCREEN_W; h = SCREEN_H; /* Generate all the nebulae backgrounds. */ nebu = noise_genNebulaeMap(w, h, NEBULAE_Z, 5.); lfile_dirMakeExist("gen"); /* Save each nebulae as an image. */ for(i = 0; i < NEBULAE_Z; i++) { snprintf(nebu_file, PATH_MAX, NEBULAE_PATH_BG, w, h, i); ret = saveNebulae(&nebu[i*w*h], w, h, nebu_file); if(ret != 0) break; /* An error has happened. */ } /* Cleanup. */ free(nebu); return ret; } /* Generate the nebulae puffs. */ static void nebu_generatePuffs(void) { int i; int w, h; SDL_Surface* sur; float* nebu; /* Generate the nebulae puffs. */ for(i = 0; i < NEBULAE_PUFFS; i++) { /* Generate the nebulae. */ w = h = RNG(20, 64); nebu = noise_genNebulaePuffMap(w, h, 1.); sur = nebu_surfaceFromNebulaeMap(nebu, w, h); free(nebu); /* Load the textures. */ nebu_pufftexs[i] = gl_loadImage(sur); } } /* Check the validity of a nebulae. 0 on success. */ static int nebu_checkCompat(const char* file) { if(lfile_fileExists(file) == 0) /* First check to see if file exists. */ return -1; return 0; } /* Save a nebulae. */ static int saveNebulae(float* map, const uint32_t w, const uint32_t h, const char* file) { char file_path[PATH_MAX]; SDL_Surface* sur; int ret; /* Fix surface. */ sur = nebu_surfaceFromNebulaeMap(map, w, h); /* Save. */ ret = 0; snprintf(file_path, PATH_MAX, "%s%s", lfile_basePath(), file); ret = SDL_savePNG(sur, file_path); /* Cleanup. */ SDL_FreeSurface(sur); return ret; } /* Load the nebulae from file. */ static SDL_Surface* loadNebulae(const char* file) { char file_path[PATH_MAX]; SDL_Surface* sur; /* Load the file. */ snprintf(file_path, PATH_MAX, "%s%s", lfile_basePath(), file); sur = IMG_Load(file_path); if(sur == NULL) { ERR("Unable to load Nebulae image: %s", file); return NULL; } return sur; } /* Generate an SDL_Surface from a 2d nebulae map. */ SDL_Surface* nebu_surfaceFromNebulaeMap(float* map, const int w, const int h) { int i; SDL_Surface* sur; uint32_t* pix; double c; /* The good surface. */ sur = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 32, RGBAMASK); pix = sur->pixels; /* Convert from mapping to actual colours. */ SDL_LockSurface(sur); for(i = 0; i < h*w; i++) { c = map[i]; pix[i] = RMASK + BMASK + GMASK + (AMASK & (uint32_t)((double)AMASK*c)); } SDL_UnlockSurface(sur); return sur; }