Add Mars surface atmosphere particels

This commit is contained in:
Thomas
2026-03-05 19:21:41 +00:00
parent 635869f226
commit 4407932a2d
4 changed files with 85 additions and 5 deletions

14
TODO.md
View File

@@ -145,11 +145,15 @@ which defers a level load into MODE_PLAY on the next frame. Tears down the
current mode (editor or play), loads the selected `.lvl` file, and seeds
`s_edit_path` so pressing E opens the same level in the editor.
## Mars Surface atmosphere particles
Mars Surface levels should have ambient floating particles (dust motes, fine
sand) drifting across the screen to sell the atmosphere. Could tie into the
existing wind system — particles drift with the wind direction. Should be
subtle, low-alpha, and layered behind/in front of the player for depth.
## ~~Mars Surface atmosphere particles~~ ✓
Implemented: `particle_emit_atmosphere_dust()` emits ambient dust motes each
frame on Mars Surface levels (keyed on `PARALLAX_STYLE_MARS`). Three
sub-layers for depth: large slow "far" motes, small quick "near" specks,
and occasional interior spawns to prevent edge seams. All particles drift
with the wind system via `gravity_scale` — wind pushes them across the
viewport. Reddish-tan color palette with per-particle variation. Low drag,
long lifetimes (3-7 s), subtle alpha fade. ~2-3 particles/frame at 60 Hz,
well within the 1024-particle pool budget.
## Skip spacecraft transition for non-surface levels
The spacecraft fly-in animation should only play on surface levels (moon01,

View File

@@ -529,3 +529,65 @@ void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir) {
};
particle_emit(&dust);
}
/* Spawn a single dust mote with the given visual properties. */
static void spawn_dust_mote(Vec2 pos, Vec2 vel,
float life_min, float life_max,
float size_min, float size_max,
float drag, float gscale,
uint8_t r, uint8_t g, uint8_t b, int vary) {
Particle *p = alloc_particle();
p->pos = pos;
p->vel = vel;
p->life = randf_range(life_min, life_max);
p->max_life = p->life;
p->size = randf_range(size_min, size_max);
p->drag = drag;
p->gravity_scale = gscale;
p->active = true;
p->color.r = clamp_u8(r + (int)randf_range(-vary, vary));
p->color.g = clamp_u8(g + (int)randf_range(-vary, vary));
p->color.b = clamp_u8(b + (int)randf_range(-vary, vary));
p->color.a = 255; /* alpha applied during render from life ratio */
}
void particle_emit_atmosphere_dust(Vec2 cam_pos, Vec2 vp) {
/* Ambient Mars dust — subtle motes drifting across the viewport.
* Two sub-layers for depth: large slow "far" motes and small quick
* "near" specks. Wind carries them; gravity_scale controls how much
* environmental forces (wind + gravity) affect each particle.
* Particles spawn along the upwind viewport edge and drift inward;
* occasional interior spawns prevent a visible edge seam. */
float wind = physics_get_wind();
float margin = 32.0f;
float dir = (wind >= 0.0f) ? 1.0f : -1.0f; /* velocity sign */
/* Upwind edge X for the two edge-spawned layers */
float edge_far = (wind >= 0.0f) ? cam_pos.x - margin
: cam_pos.x + vp.x + margin;
float edge_near = (wind >= 0.0f) ? cam_pos.x - margin * 0.5f
: cam_pos.x + vp.x + margin * 0.5f;
/* Far dust motes — large, slow, translucent (1/frame) */
spawn_dust_mote(
vec2(edge_far, cam_pos.y + randf() * vp.y),
vec2(dir * randf_range(8.0f, 25.0f), randf_range(-6.0f, 6.0f)),
4.0f, 7.0f, 1.5f, 3.0f, 0.3f, 0.08f,
180, 140, 100, 25);
/* Near dust specks — small, quicker, brighter (1/frame) */
spawn_dust_mote(
vec2(edge_near, cam_pos.y + randf() * vp.y),
vec2(dir * randf_range(15.0f, 40.0f), randf_range(-10.0f, 10.0f)),
2.5f, 5.0f, 0.8f, 1.5f, 0.2f, 0.12f,
200, 160, 120, 20);
/* Occasional interior spawn — prevents edge seam on calm wind */
if (rand() % 3 == 0) {
spawn_dust_mote(
vec2(cam_pos.x + randf() * vp.x, cam_pos.y + randf() * vp.y),
vec2(randf_range(-5.0f, 5.0f), randf_range(-8.0f, 3.0f)),
3.0f, 6.0f, 1.0f, 2.5f, 0.4f, 0.06f,
160, 130, 95, 25);
}
}

View File

@@ -89,4 +89,9 @@ void particle_emit_muzzle_flash(Vec2 pos, Vec2 shoot_dir);
/* Wall slide dust (small puffs while scraping against a wall) */
void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir);
/* Ambient atmosphere dust (call each frame for Mars Surface levels).
* Spawns subtle dust motes around the camera viewport that drift with wind.
* cam_pos = camera top-left world position, vp = viewport size in pixels. */
void particle_emit_atmosphere_dust(Vec2 cam_pos, Vec2 vp);
#endif /* JNR_PARTICLE_H */

View File

@@ -578,6 +578,15 @@ void level_update(Level *level, float dt) {
}
}
/* Emit ambient atmosphere dust on Mars Surface levels before the
* particle update pass so new motes get their first physics step
* this frame — consistent with other per-frame emitters. */
if (level->map.parallax_style == PARALLAX_STYLE_MARS) {
particle_emit_atmosphere_dust(
level->camera.pos,
level->camera.viewport);
}
/* Update particles */
particle_update(dt);