forked from tas/major_tom
Initial commit
This commit is contained in:
296
src/engine/parallax.c
Normal file
296
src/engine/parallax.c
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "engine/parallax.h"
|
||||
#include "engine/camera.h"
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
/* ── Helpers ─────────────────────────────────────────── */
|
||||
|
||||
static float randf(void) {
|
||||
return (float)rand() / (float)RAND_MAX;
|
||||
}
|
||||
|
||||
static uint8_t clamp_u8(int v) {
|
||||
if (v < 0) return 0;
|
||||
if (v > 255) return 255;
|
||||
return (uint8_t)v;
|
||||
}
|
||||
|
||||
/* ── Init / Free ─────────────────────────────────────── */
|
||||
|
||||
void parallax_init(Parallax *p) {
|
||||
memset(p, 0, sizeof(Parallax));
|
||||
}
|
||||
|
||||
void parallax_set_far(Parallax *p, SDL_Texture *tex, float scroll_x, float scroll_y) {
|
||||
p->far_layer.texture = tex;
|
||||
p->far_layer.scroll_x = scroll_x;
|
||||
p->far_layer.scroll_y = scroll_y;
|
||||
p->far_layer.active = true;
|
||||
p->far_layer.owns_texture = false; /* asset manager owns it */
|
||||
SDL_QueryTexture(tex, NULL, NULL, &p->far_layer.tex_w, &p->far_layer.tex_h);
|
||||
}
|
||||
|
||||
void parallax_set_near(Parallax *p, SDL_Texture *tex, float scroll_x, float scroll_y) {
|
||||
p->near_layer.texture = tex;
|
||||
p->near_layer.scroll_x = scroll_x;
|
||||
p->near_layer.scroll_y = scroll_y;
|
||||
p->near_layer.active = true;
|
||||
p->near_layer.owns_texture = false; /* asset manager owns it */
|
||||
SDL_QueryTexture(tex, NULL, NULL, &p->near_layer.tex_w, &p->near_layer.tex_h);
|
||||
}
|
||||
|
||||
/* ── Procedural star generation ──────────────────────── */
|
||||
|
||||
void parallax_generate_stars(Parallax *p, SDL_Renderer *renderer) {
|
||||
/* Create a 640x360 texture (screen-sized, tiles seamlessly) */
|
||||
int w = SCREEN_WIDTH;
|
||||
int h = SCREEN_HEIGHT;
|
||||
|
||||
SDL_Texture *tex = SDL_CreateTexture(renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, w, h);
|
||||
if (!tex) return;
|
||||
|
||||
SDL_SetRenderTarget(renderer, tex);
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
/* Clear to transparent (bg_color will show through) */
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
/* Seed for reproducible star patterns */
|
||||
unsigned int saved_seed = (unsigned int)rand();
|
||||
srand(42);
|
||||
|
||||
/* Small dim stars (distant) — lots of them */
|
||||
for (int i = 0; i < 120; i++) {
|
||||
int x = (int)(randf() * w);
|
||||
int y = (int)(randf() * h);
|
||||
uint8_t brightness = (uint8_t)(100 + (int)(randf() * 80));
|
||||
/* Slight color tints: bluish, yellowish, or white */
|
||||
uint8_t r = brightness, g = brightness, b = brightness;
|
||||
float tint = randf();
|
||||
if (tint < 0.2f) {
|
||||
b = clamp_u8(brightness + 40); /* blue tint */
|
||||
} else if (tint < 0.35f) {
|
||||
r = clamp_u8(brightness + 30);
|
||||
g = clamp_u8(brightness + 20); /* warm tint */
|
||||
}
|
||||
SDL_SetRenderDrawColor(renderer, r, g, b, (uint8_t)(150 + (int)(randf() * 105)));
|
||||
SDL_Rect dot = {x, y, 1, 1};
|
||||
SDL_RenderFillRect(renderer, &dot);
|
||||
}
|
||||
|
||||
/* Medium stars */
|
||||
for (int i = 0; i < 30; i++) {
|
||||
int x = (int)(randf() * w);
|
||||
int y = (int)(randf() * h);
|
||||
uint8_t brightness = (uint8_t)(180 + (int)(randf() * 75));
|
||||
uint8_t r = brightness, g = brightness, b = brightness;
|
||||
float tint = randf();
|
||||
if (tint < 0.25f) {
|
||||
b = 255; r = clamp_u8(brightness - 20); /* blue star */
|
||||
} else if (tint < 0.4f) {
|
||||
r = 255; g = clamp_u8(brightness - 10); /* warm star */
|
||||
}
|
||||
SDL_SetRenderDrawColor(renderer, r, g, b, 255);
|
||||
SDL_Rect dot = {x, y, 1, 1};
|
||||
SDL_RenderFillRect(renderer, &dot);
|
||||
/* Cross-halo for slightly brighter stars */
|
||||
if (randf() < 0.4f) {
|
||||
SDL_SetRenderDrawColor(renderer, r, g, b, 80);
|
||||
SDL_Rect h1 = {x - 1, y, 3, 1};
|
||||
SDL_Rect h2 = {x, y - 1, 1, 3};
|
||||
SDL_RenderFillRect(renderer, &h1);
|
||||
SDL_RenderFillRect(renderer, &h2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Bright feature stars (few, prominent) */
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int x = (int)(randf() * w);
|
||||
int y = (int)(randf() * h);
|
||||
/* Core pixel */
|
||||
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
||||
SDL_Rect core = {x, y, 2, 2};
|
||||
SDL_RenderFillRect(renderer, &core);
|
||||
/* Glow cross */
|
||||
uint8_t glow_a = (uint8_t)(120 + (int)(randf() * 80));
|
||||
float tint = randf();
|
||||
uint8_t gr = 200, gg = 210, gb = 255; /* default blue-white */
|
||||
if (tint < 0.3f) { gr = 255; gg = 220; gb = 180; } /* warm */
|
||||
SDL_SetRenderDrawColor(renderer, gr, gg, gb, glow_a);
|
||||
SDL_Rect cross_h = {x - 1, y, 4, 2};
|
||||
SDL_Rect cross_v = {x, y - 1, 2, 4};
|
||||
SDL_RenderFillRect(renderer, &cross_h);
|
||||
SDL_RenderFillRect(renderer, &cross_v);
|
||||
}
|
||||
|
||||
srand(saved_seed); /* restore randomness */
|
||||
|
||||
SDL_SetRenderTarget(renderer, NULL);
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
|
||||
p->far_layer.texture = tex;
|
||||
p->far_layer.tex_w = w;
|
||||
p->far_layer.tex_h = h;
|
||||
p->far_layer.scroll_x = 0.05f;
|
||||
p->far_layer.scroll_y = 0.05f;
|
||||
p->far_layer.active = true;
|
||||
p->far_layer.owns_texture = true;
|
||||
}
|
||||
|
||||
/* ── Procedural nebula generation ────────────────────── */
|
||||
|
||||
void parallax_generate_nebula(Parallax *p, SDL_Renderer *renderer) {
|
||||
int w = SCREEN_WIDTH;
|
||||
int h = SCREEN_HEIGHT;
|
||||
|
||||
SDL_Texture *tex = SDL_CreateTexture(renderer,
|
||||
SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, w, h);
|
||||
if (!tex) return;
|
||||
|
||||
SDL_SetRenderTarget(renderer, tex);
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
|
||||
/* Clear to transparent */
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
unsigned int saved_seed = (unsigned int)rand();
|
||||
srand(137);
|
||||
|
||||
/* Paint soft nebula blobs — clusters of semi-transparent rects
|
||||
* that overlap to create soft, cloudy shapes */
|
||||
|
||||
/* Nebula color palette (space purples, blues, teals) */
|
||||
typedef struct { uint8_t r, g, b; } NebColor;
|
||||
NebColor palette[] = {
|
||||
{ 60, 20, 100}, /* deep purple */
|
||||
{ 30, 40, 120}, /* dark blue */
|
||||
{ 20, 60, 90}, /* teal */
|
||||
{ 80, 15, 60}, /* magenta */
|
||||
{ 40, 50, 100}, /* slate blue */
|
||||
};
|
||||
int palette_count = sizeof(palette) / sizeof(palette[0]);
|
||||
|
||||
/* Paint 4-5 nebula clouds */
|
||||
for (int cloud = 0; cloud < 5; cloud++) {
|
||||
float cx = randf() * w;
|
||||
float cy = randf() * h;
|
||||
NebColor col = palette[cloud % palette_count];
|
||||
|
||||
/* Each cloud is ~30-50 overlapping soft rects */
|
||||
int blobs = 30 + (int)(randf() * 20);
|
||||
for (int b = 0; b < blobs; b++) {
|
||||
float angle = randf() * (float)(2.0 * M_PI);
|
||||
float dist = randf() * 80.0f + randf() * 40.0f;
|
||||
int bx = (int)(cx + cosf(angle) * dist);
|
||||
int by = (int)(cy + sinf(angle) * dist);
|
||||
int bw = 8 + (int)(randf() * 20);
|
||||
int bh = 8 + (int)(randf() * 16);
|
||||
|
||||
/* Vary color slightly per blob */
|
||||
uint8_t br = clamp_u8(col.r + (int)(randf() * 30 - 15));
|
||||
uint8_t bg = clamp_u8(col.g + (int)(randf() * 30 - 15));
|
||||
uint8_t bb = clamp_u8(col.b + (int)(randf() * 30 - 15));
|
||||
uint8_t ba = (uint8_t)(8 + (int)(randf() * 18)); /* very subtle */
|
||||
|
||||
SDL_SetRenderDrawColor(renderer, br, bg, bb, ba);
|
||||
SDL_Rect rect = {bx - bw / 2, by - bh / 2, bw, bh};
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
}
|
||||
}
|
||||
|
||||
/* Scattered dust particles (tiny dim dots between clouds) */
|
||||
for (int i = 0; i < 60; i++) {
|
||||
int x = (int)(randf() * w);
|
||||
int y = (int)(randf() * h);
|
||||
NebColor col = palette[(int)(randf() * palette_count)];
|
||||
SDL_SetRenderDrawColor(renderer, col.r, col.g, col.b,
|
||||
(uint8_t)(30 + (int)(randf() * 40)));
|
||||
SDL_Rect dot = {x, y, 2, 2};
|
||||
SDL_RenderFillRect(renderer, &dot);
|
||||
}
|
||||
|
||||
srand(saved_seed);
|
||||
|
||||
SDL_SetRenderTarget(renderer, NULL);
|
||||
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
|
||||
|
||||
p->near_layer.texture = tex;
|
||||
p->near_layer.tex_w = w;
|
||||
p->near_layer.tex_h = h;
|
||||
p->near_layer.scroll_x = 0.15f;
|
||||
p->near_layer.scroll_y = 0.10f;
|
||||
p->near_layer.active = true;
|
||||
p->near_layer.owns_texture = true;
|
||||
}
|
||||
|
||||
/* ── Render ──────────────────────────────────────────── */
|
||||
|
||||
static void render_layer(const ParallaxLayer *layer, const Camera *cam,
|
||||
SDL_Renderer *renderer) {
|
||||
if (!layer->active || !layer->texture) return;
|
||||
|
||||
int tw = layer->tex_w;
|
||||
int th = layer->tex_h;
|
||||
if (tw <= 0 || th <= 0) return;
|
||||
|
||||
/* Calculate scroll offset from camera position */
|
||||
float offset_x = cam->pos.x * layer->scroll_x;
|
||||
float offset_y = cam->pos.y * layer->scroll_y;
|
||||
|
||||
/* Wrap to texture bounds (fmod that handles negatives) */
|
||||
int ix = (int)offset_x % tw;
|
||||
int iy = (int)offset_y % th;
|
||||
if (ix < 0) ix += tw;
|
||||
if (iy < 0) iy += th;
|
||||
|
||||
/* Tile the texture across the screen.
|
||||
* We need to cover SCREEN_WIDTH x SCREEN_HEIGHT, starting
|
||||
* from the wrapped offset. Draw a 2x2 grid of tiles to
|
||||
* ensure full coverage regardless of offset. */
|
||||
for (int ty = -1; ty <= 1; ty++) {
|
||||
for (int tx = -1; tx <= 1; tx++) {
|
||||
SDL_Rect dst = {
|
||||
tx * tw - ix,
|
||||
ty * th - iy,
|
||||
tw,
|
||||
th
|
||||
};
|
||||
/* Skip tiles entirely off-screen */
|
||||
if (dst.x + dst.w < 0 || dst.x >= SCREEN_WIDTH) continue;
|
||||
if (dst.y + dst.h < 0 || dst.y >= SCREEN_HEIGHT) continue;
|
||||
|
||||
SDL_RenderCopy(renderer, layer->texture, NULL, &dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parallax_render(const Parallax *p, const Camera *cam, SDL_Renderer *renderer) {
|
||||
if (!p || !cam) return;
|
||||
render_layer(&p->far_layer, cam, renderer);
|
||||
render_layer(&p->near_layer, cam, renderer);
|
||||
}
|
||||
|
||||
/* ── Free ────────────────────────────────────────────── */
|
||||
|
||||
void parallax_free(Parallax *p) {
|
||||
/* Only free textures we generated — asset manager owns file-loaded ones */
|
||||
if (p->far_layer.texture && p->far_layer.owns_texture) {
|
||||
SDL_DestroyTexture(p->far_layer.texture);
|
||||
}
|
||||
p->far_layer.texture = NULL;
|
||||
p->far_layer.active = false;
|
||||
|
||||
if (p->near_layer.texture && p->near_layer.owns_texture) {
|
||||
SDL_DestroyTexture(p->near_layer.texture);
|
||||
}
|
||||
p->near_layer.texture = NULL;
|
||||
p->near_layer.active = false;
|
||||
}
|
||||
Reference in New Issue
Block a user