diff --git a/src/render3d.rs b/src/render3d.rs index 058fe48..afa6785 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -1148,11 +1148,42 @@ impl R3d { push(vec3(x, tower_height(tw) + 0.6, z), 4.5, vec3(0.75, 0.4, 1.0), 0.8 + tw.flash * 3.0, 10.0); } } + // effects light up their surroundings: lightning, explosions, glowing projectiles, spells + let tint = |c: u32| { + let c = rgba(c); + vec3(c.r, c.g, c.b) + }; for f in &g.fx { - if f.kind == 1 { - push(vec3(f.x, 2.0, f.y), 8.0, vec3(1.0, 1.0, 0.85), 3.0 * (1.0 - f.t / f.max), 200.0); + let left = 1.0 - (f.t / f.max).clamp(0.0, 1.0); + match f.kind { + 1 => push(vec3(f.x, 2.0, f.y), 8.0, vec3(1.0, 1.0, 0.85), 3.0 * left, 200.0), + 0 => { + use crate::particles::*; + let c = match f.style { + ST_FIRE => Some(vec3(1.0, 0.55, 0.15)), + ST_SHADOW => Some(vec3(0.75, 0.3, 1.0)), + ST_HOLY => Some(vec3(1.0, 0.92, 0.6)), + ST_ARCANE => Some(vec3(1.0, 0.7, 0.4)), + ST_RUNE => Some(vec3(0.4, 0.8, 1.0)), + ST_MAGIC => Some(vec3(1.0, 0.4, 0.9)), + _ => None, + }; + if let (Some(c), true) = (c, left > 0.0) { + let (x, z, h) = fx_pos(f, 1.0 - left); + push(vec3(x, h, z), 3.0 + f.size, c, 1.4, 120.0); + } + } + // healing and spells (level ups, specials): a glow in the effect's colour + 4 => push(vec3(f.x, 0.8, f.y), 4.0, vec3(0.5, 1.0, 0.55), 1.6 * left, 130.0), + 7 => push(vec3(f.x, 0.9, f.y), 5.0, tint(f.col), 2.0 * left, 130.0), + _ => {} } } + // explosions (fireballs, cannonballs, rune strikes): a bright, short flash of light + for p in g.parts.iter().filter(|p| p.kind == crate::particles::FLASH) { + let left = 1.0 - (p.t / p.max).clamp(0.0, 1.0); + push(vec3(p.x, p.h + 0.4, p.y), 3.0 + 4.0 * p.size, tint(p.col), 3.5 * left * left.sqrt(), 180.0); + } l.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); l.truncate(self.max_lights.min(MAX_LIGHTS)); let mut pos = vec![Vec4::ZERO; MAX_LIGHTS]; @@ -2620,11 +2651,52 @@ fn draw_traps_glow(g: &Game, t: f32) { // particles that glow (embers, sparks, smoke, flashes, shock waves) fn draw_scorch_glow(g: &Game) { use crate::particles::*; + // all marks go into one mesh (one draw call) + let mut v: Vec = Vec::new(); + let mut ix: Vec = Vec::new(); + let mut disc = |c: Vec3, r: f32, color: Color, seg: usize| { + if v.len() + seg + 1 > 60000 { + return; + } + let cc = [(color.r * 255.0) as u8, (color.g * 255.0) as u8, (color.b * 255.0) as u8, (color.a * 255.0) as u8]; + let b = v.len() as u16; + v.push(Vertex { position: c, uv: Vec2::ZERO, color: cc, normal: Vec4::Y }); + for k in 0..seg { + let a = k as f32 / seg as f32 * std::f32::consts::TAU; + v.push(Vertex { position: c + vec3(a.cos() * r, 0.0, a.sin() * r), uv: Vec2::ZERO, color: cc, normal: Vec4::Y }); + } + for k in 0..seg as u16 { + ix.extend_from_slice(&[b, b + 1 + k, b + 1 + (k + 1) % seg as u16]); + } + }; for p in g.stuck.iter().filter(|p| p.kind == SCORCH) { - let fade = 1.0 - (p.t / p.max).clamp(0.0, 1.0); - let at = vec3(p.x, floor_y(g, p.x, p.y) + 0.018, p.y); - disc(at, p.size, Color::new(0.02, 0.017, 0.017, 0.78 * fade), 14); - ring(at + Vec3::Y * 0.002, p.size * 1.06, Color::new(0.55, 0.22, 0.06, 0.22 * fade)); + let life = (p.t / p.max).clamp(0.0, 1.0); + // stays dark for most of its time, then fades + let fade = if life < 0.6 { 1.0 } else { 1.0 - (life - 0.6) / 0.4 }; + let base = vec3(p.x, floor_y(g, p.x, p.y) + 0.016, p.y); + // a ragged blotch of soot: overlapping soft spots around the centre, their places + // fixed by the position (so the mark keeps its shape) + let mut h = ((p.x * 73.0) as i32 as u32).wrapping_mul(2654435761) ^ ((p.y * 91.0) as i32 as u32).wrapping_mul(2246822519); + let mut next = || { + h ^= h >> 13; + h = h.wrapping_mul(1274126177); + (h >> 8) as f32 / 16777216.0 + }; + for k in 0..6 { + let (a, d) = (next() * std::f32::consts::TAU, if k == 0 { 0.0 } else { p.size * (0.25 + 0.3 * next()) }); + let r = p.size * if k == 0 { 0.65 } else { 0.3 + 0.25 * next() }; + let at = base + vec3(a.cos() * d, 0.0005 * k as f32, a.sin() * d); + disc(at, r, Color::new(0.03, 0.025, 0.022, 0.32 * fade), 10); + disc(at, r * 0.6, Color::new(0.015, 0.012, 0.01, 0.3 * fade), 8); + } + // freshly burnt: embers glow in the middle for a few seconds + if p.t < 4.0 { + let k = 1.0 - p.t / 4.0; + disc(base + Vec3::Y * 0.004, p.size * 0.45, Color::new(1.0, 0.38, 0.08, 0.45 * k * k), 12); + } + } + if !ix.is_empty() { + draw_mesh(&Mesh { vertices: v, indices: ix, texture: None }); } } @@ -2642,8 +2714,8 @@ fn draw_parts_glow(g: &Game) { SMOKE => draw_sphere(at, p.size, None, Color::new(c.r, c.g, c.b, 0.14 * (1.0 - life))), FLASH => { let r = p.size * (0.3 + life * 0.9); - draw_sphere(at, r * 0.6, None, Color::new(1.0, 0.9, 0.7, 0.7 * (1.0 - life))); - draw_sphere(at, r, None, Color::new(c.r, c.g, c.b, 0.35 * (1.0 - life))); + draw_sphere(at, r * 0.6, None, Color::new(1.0, 0.95, 0.8, 0.95 * (1.0 - life))); + draw_sphere(at, r, None, Color::new(c.r, c.g, c.b, 0.55 * (1.0 - life))); } RING => ring(at, p.size * (0.2 + life), Color::new(c.r, c.g, c.b, 0.8 * (1.0 - life))), _ => {}