/* Copyright (C) 2009 Vegard Nossum * * To compile: g++ -Wall -g -I/usr/include/SDL fireworks.cc -lSDL -lGL -lGLU */ extern "C" { #include #include #include } #include #include #include #include #include #include extern "C" { #include #include #include } #define CONFIG_FPS 60 /* Size of the window */ #define CONFIG_WINDOW_WIDTH 1280 #define CONFIG_WINDOW_HEIGHT 800 /* Size of the game screen */ #define CONFIG_SCREEN_WIDTH 128 #define CONFIG_SCREEN_HEIGHT 128 #define CONFIG_GRAVITY 9.8f static double random(double a, double b) { return a + (b - a) * rand() / RAND_MAX; } static double clamp(double x, double a, double b) { if (x < a) return a; if (x > b) return b; return x; } static void hsv_to_rgb(double h, double s, double v, double &r, double &g, double &b) { /* http://en.wikipedia.org/wiki/HSL_and_HSV */ unsigned int h_i = (unsigned int) floor(h / 60) % 6; double f = h / 60 - floor(h / 60); double p = v * (1 - s); double q = v * (1 - f * s); double t = v * (1 - (1 - f) * s); switch (h_i) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; case 5: r = v; g = p; b = q; break; } } class particle { public: particle(double px, double py, double vx, double vy, double m, double size, double r, double g, double b, double a); ~particle(); public: void draw(); public: unsigned int _age; double _px, _py; double _vx, _vy; double _m; double _size; double _r, _g, _b, _a; }; typedef std::set particle_set; static particle_set particles; static particle_set removed_particles; particle::particle(double px, double py, double vx, double vy, double m, double size, double r, double g, double b, double a): _age(0), _px(px), _py(py), _vx(vx), _vy(vy), _m(m), _size(size), _r(r), _g(g), _b(b), _a(a) { } particle::~particle() { } void particle::draw() { if (_age > 75 && rand() % 100 < 10) glColor4f(_r, _g, _b, 1); else if (_age > 100 && rand() % 100 < 5) glColor4f(_r, _g, _b, 0.2); else glColor4f(_r, _g, _b, _a * (100.0f - _age) / 100. + random(-2e-1, 2e-1)); #if 1 glLineWidth(_size); glBegin(GL_LINES); glVertex2f(_px, _py); glVertex2f(_px + _vx, _py + _vy); glEnd(); #endif glPointSize(_size); glBegin(GL_POINTS); glVertex2f(_px, _py); glEnd(); if (_age < 80) { glColor4f(1, 1, 1, 1 - _age / 80.); glPointSize(_size - 1); glBegin(GL_POINTS); glVertex2f(_px + _vx, _py + _vy); glEnd(); } _px += _vx; _py += _vy; /* Friction */ _vx *= 0.97; _vy *= 0.97; _vy += _m / CONFIG_GRAVITY; _m *= 0.98; ++_age; if (_age == 115) removed_particles.insert(this); } class explosion { public: explosion(); ~explosion(); public: void draw(); void audio(int16_t *buffer, unsigned int len); public: unsigned int _age; unsigned int _max_age; double _fc1; double _fc2; double _offset; }; typedef std::set explosion_set; static explosion_set explosions; static explosion_set removed_explosions; explosion::explosion(): _age(0), _max_age(115), _fc1(random(49, 52)), _fc2(random(10, 15)), _offset(0) { } explosion::~explosion() { } void explosion::draw() { ++_age; if (_age == _max_age) removed_explosions.insert(this); } void explosion::audio(int16_t *buffer, unsigned int len) { for (unsigned int i = 0; i < len; ++i) { // double boom = (1 - (1. * _age / _max_age)) * 2 * 1024 * sin(_fc1 * pow(cos(_fc2 * _offset), 3) * _offset * M_PI / (44100.0 / 2)); double boom = pow((1 - (1. * _age / _max_age)), 5) * 256 * sin(10 * _fc1 * (fmod(_offset, 30) + fmod(_offset, 50)) * _offset * M_PI / (44100.0 / 2)); if (_age < 20) boom += 4 * 1024; double sparkle = (1 - (1. * _age / _max_age)) * 2 * 1024 * ((fmod(_offset, 10 * _fc1) + fmod(_offset, 10 * _fc2)) < 20 ? -1 : 1); double noise = (1. * _age / _max_age) * 256 * random(-1, 1); buffer[i] = clamp(buffer[i] + boom + sparkle + noise, -32767, 32767); _offset++; } } class rocket { public: rocket(unsigned int max_age, double px, double py, double vx, double vy, double m, double freq, char pattern = 0, unsigned int delay = 0); ~rocket(); public: void draw(); void audio(int16_t *buffer, unsigned int len); public: unsigned int _age; unsigned int _max_age; double _px, _py; double _vx, _vy; double _m; /* Audio: */ double _freq; double _offset; /* Pattern */ char _pattern; unsigned int _delay; }; /* happy new year! * aehnprwy */ static char pat_A[64] = { 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_E[64] = { 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_H[64] = { 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_N[64] = { 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_P[64] = { 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_R[64] = { 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_W[64] = { 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char pat_Y[64] = { 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const char *char_to_pattern(char ch) { switch (ch) { case 'A': return pat_A; case 'E': return pat_E; case 'H': return pat_H; case 'N': return pat_N; case 'P': return pat_P; case 'R': return pat_R; case 'W': return pat_W; case 'Y': return pat_Y; } return NULL; } static void new_explosion(rocket *rocket, unsigned int n, double px, double py, double vx, double vy, double m, double size, double r, double g, double b) { for (unsigned int i = 0; i < n; ++i) { double x, y; if (rocket->_pattern == 0) { double a = random(0, 2 * M_PI); double l = random(0, 1); x = l * cos(a); y = l * sin(a); } else { const char *pattern = char_to_pattern(rocket->_pattern); if (!pattern) return; x = random(-1, 1); y = random(-1, 1); unsigned int pat_x = 4 + 4 * x; unsigned int pat_y = 4 + 4 * y; assert(pat_x >= 0 && pat_x < 8); assert(pat_y >= 0 && pat_y < 8); if (!pattern[pat_x + pat_y * 8]) continue; } particles.insert(new particle(px, py, vx + x, vy + y, m / n, size, r + random(-1e-1, 1e-1), g + random(-1e-1, 1e-1), b + random(-1e-1, 1e-1), 1)); } SDL_LockAudio(); explosions.insert(new explosion()); SDL_UnlockAudio(); } typedef std::set rocket_set; static rocket_set rockets; static rocket_set removed_rockets; rocket::rocket(unsigned int max_age, double px, double py, double vx, double vy, double m, double freq, char pattern, unsigned int delay): _age(0), _max_age(max_age), _px(px), _py(py), _vx(vx), _vy(vy), _m(m), _freq(freq), _offset(0), _pattern(pattern), _delay(delay) { } rocket::~rocket() { } void rocket::draw() { if (_delay > 0) { --_delay; return; } glColor4f(0.75, 0.4, 0, 1); glLineWidth(3); glBegin(GL_LINES); glVertex2f(_px, _py); glVertex2f(_px + _vx, _py + _vy); glEnd(); glPointSize(3); glBegin(GL_POINTS); glVertex2f(_px, _py); glEnd(); _px += _vx; _py += _vy; _vy += _m / CONFIG_GRAVITY; ++_age; if (_age == _max_age) { double r, g, b; hsv_to_rgb(random(0, 360), random(0.8, 1), 1, r, g, b); new_explosion(this, random(200, 400), _px, _py, _vx, _vy, 75, 3, r, g, b); removed_rockets.insert(this); } } void rocket::audio(int16_t *buffer, unsigned int len) { double freq = _freq; double offset = _offset; double doffset = clamp((180 - _age) / 100., 0, 10); for (unsigned int i = 0; i < len; ++i) { buffer[i] = clamp(buffer[i] + 1024 * (pow(sin(freq * offset * M_PI / (44100.0 / 2)), 2) - 0.5), -32767, 32767); offset += doffset; } _offset = offset; } static void new_rocket(unsigned int max_age, double px, double py, double a, double v, double m, double freq) { SDL_LockAudio(); rockets.insert(new rocket(max_age, px, py, v * cos(a), v * -sin(a), m, freq)); SDL_UnlockAudio(); } static void fire() { new_rocket(random(60, 80), random(-32, 32), 128, random(80, 100) * M_PI / 180.0, random(2.8, 4.2), 0.4, 3 * random(440, 880)); } static void launch_string(double vy, unsigned int delay, const char *str) { static const unsigned int spacing = 32; unsigned int n = strlen(str); SDL_LockAudio(); for (unsigned int i = 0; i < n; ++i) { rockets.insert(new rocket(50, 0.0 - n * spacing / 2 + spacing * i, 128, 0, -vy, 0.4, 3 * 440, str[i], delay + 10 * i)); } SDL_UnlockAudio(); } static void init() { srand(time(NULL)); //fire(); } static void deinit() { } static double aspect; static void resize(int width, int height) { if(width == 0 || height == 0) return; aspect = 1.0 * height / width; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glViewport(0, 0, width, height); } static bool automatic = true; static void display() { static unsigned int frame = 0; if (frame == 0) glClear(GL_COLOR_BUFFER_BIT); ++frame; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluOrtho2D(0, 1, 0, 1); glColor4f(0, 0, 0, 0.38); glBegin(GL_QUADS); glVertex2f(0, 0); glVertex2f(0, 1); glVertex2f(1, 1); glVertex2f(1, 0); glEnd(); glLoadIdentity(); gluOrtho2D(-CONFIG_SCREEN_WIDTH, CONFIG_SCREEN_WIDTH, CONFIG_SCREEN_HEIGHT * aspect, -CONFIG_SCREEN_HEIGHT * aspect); for (particle_set::iterator i = particles.begin(), end = particles.end(); i != end; ++i) { particle *p = *i; p->draw(); } for (particle_set::iterator i = removed_particles.begin(), end = removed_particles.end(); i != end; ++i) { particle *p = *i; particles.erase(p); delete p; } removed_particles.clear(); for (explosion_set::iterator i = explosions.begin(), end = explosions.end(); i != end; ++i) { explosion *r = *i; r->draw(); } for (explosion_set::iterator i = removed_explosions.begin(), end = removed_explosions.end(); i != end; ++i) { explosion *r = *i; SDL_LockAudio(); explosions.erase(r); SDL_UnlockAudio(); delete r; } removed_explosions.clear(); for (rocket_set::iterator i = rockets.begin(), end = rockets.end(); i != end; ++i) { rocket *r = *i; r->draw(); } for (rocket_set::iterator i = removed_rockets.begin(), end = removed_rockets.end(); i != end; ++i) { rocket *r = *i; SDL_LockAudio(); rockets.erase(r); SDL_UnlockAudio(); delete r; } removed_rockets.clear(); SDL_GL_SwapBuffers(); if (automatic) { if (rand() % 1000 < 24) fire(); if (rand() % 1000 < 1) { launch_string(4.0, 0, "HAPPY"); launch_string(3.5, 50, "NEW"); launch_string(3.0, 100, "YEAR"); } } } static Uint32 displayTimer(Uint32 interval, void* param) { SDL_Event event; event.type = SDL_USEREVENT; SDL_PushEvent(&event); return interval; } static void keyboard(SDL_KeyboardEvent* key) { switch(key->keysym.sym) { case SDLK_SPACE: fire(); break; case SDLK_RETURN: launch_string(4.0, 0, "HAPPY"); launch_string(3.5, 50, "NEW"); launch_string(3.0, 100, "YEAR"); break; case SDLK_ESCAPE: { SDL_Event event; event.type = SDL_QUIT; SDL_PushEvent(&event); } break; default: printf("key %d\n", key->keysym.sym); } } static void keyboardUp(SDL_KeyboardEvent* key) { switch(key->keysym.sym) { default: break; } } static void audio(void *unused, Uint8 *stream, int len) { for (rocket_set::iterator i = rockets.begin(), end = rockets.end(); i != end; ++i) { rocket *r = *i; r->audio((int16_t *) stream, (unsigned int) len / 2); } for (explosion_set::iterator i = explosions.begin(), end = explosions.end(); i != end; ++i) { explosion *e = *i; e->audio((int16_t *) stream, (unsigned int) len / 2); } } int main(int argc, char *argv[]) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) == -1) { fprintf(stderr, "error: %s\n", SDL_GetError()); exit(1); } SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); SDL_Surface* surface = SDL_SetVideoMode(CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_HEIGHT, 0, SDL_OPENGL); if (!surface) { fprintf(stderr, "error: %s\n", SDL_GetError()); exit(1); } SDL_AudioSpec fmt; fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 1; fmt.samples = 512; fmt.callback = &audio; fmt.userdata = NULL; if (SDL_OpenAudio(&fmt, NULL) < 0) { fprintf(stderr, "error: %s\n", SDL_GetError()); exit(1); } printf("%d %d[%d] %d %d\n", fmt.freq, fmt.format, AUDIO_S16, fmt.channels, fmt.samples); init(); SDL_PauseAudio(0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); glEnable(GL_POINT_SMOOTH); glEnable(GL_LINE_SMOOTH); glClearColor(0, 0, 0, 1); resize(CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_HEIGHT); SDL_TimerID display_timer = SDL_AddTimer(1000 / CONFIG_FPS, &displayTimer, NULL); if (!display_timer) { fprintf(stderr, "error: %s\n", SDL_GetError()); exit(1); } bool running = true; while (running) { SDL_Event event; SDL_WaitEvent(&event); switch (event.type) { case SDL_USEREVENT: display(); break; case SDL_KEYDOWN: keyboard(&event.key); break; case SDL_KEYUP: keyboardUp(&event.key); break; case SDL_MOUSEBUTTONDOWN: break; case SDL_QUIT: running = false; break; case SDL_VIDEORESIZE: resize(event.resize.w, event.resize.h); break; } } deinit(); SDL_CloseAudio(); SDL_Quit(); return 0; }