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 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. `s_edit_path` so pressing E opens the same level in the editor.
## Mars Surface atmosphere particles ## ~~Mars Surface atmosphere particles~~ ✓
Mars Surface levels should have ambient floating particles (dust motes, fine Implemented: `particle_emit_atmosphere_dust()` emits ambient dust motes each
sand) drifting across the screen to sell the atmosphere. Could tie into the frame on Mars Surface levels (keyed on `PARALLAX_STYLE_MARS`). Three
existing wind system — particles drift with the wind direction. Should be sub-layers for depth: large slow "far" motes, small quick "near" specks,
subtle, low-alpha, and layered behind/in front of the player for depth. 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 ## Skip spacecraft transition for non-surface levels
The spacecraft fly-in animation should only play on surface levels (moon01, 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); 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) */ /* Wall slide dust (small puffs while scraping against a wall) */
void particle_emit_wall_slide_dust(Vec2 pos, int wall_dir); 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 */ #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 */ /* Update particles */
particle_update(dt); particle_update(dt);