Lephisto/src/nebulae.c

533 lines
15 KiB
C

#include <errno.h>
#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;
}