Ragged scorch marks with fresh embers; explosions, projectiles and spells cast light

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 15:13:15 +01:00
1 parent 2a6892f9a6
commit 730ad08d7a
1 file changed
+80 -8
+80 -8
View File
@@ -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); 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 { for f in &g.fx {
if f.kind == 1 { let left = 1.0 - (f.t / f.max).clamp(0.0, 1.0);
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); 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.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
l.truncate(self.max_lights.min(MAX_LIGHTS)); l.truncate(self.max_lights.min(MAX_LIGHTS));
let mut pos = vec![Vec4::ZERO; 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) // particles that glow (embers, sparks, smoke, flashes, shock waves)
fn draw_scorch_glow(g: &Game) { fn draw_scorch_glow(g: &Game) {
use crate::particles::*; use crate::particles::*;
// all marks go into one mesh (one draw call)
let mut v: Vec<Vertex> = Vec::new();
let mut ix: Vec<u16> = 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) { 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 life = (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); // stays dark for most of its time, then fades
disc(at, p.size, Color::new(0.02, 0.017, 0.017, 0.78 * fade), 14); let fade = if life < 0.6 { 1.0 } else { 1.0 - (life - 0.6) / 0.4 };
ring(at + Vec3::Y * 0.002, p.size * 1.06, Color::new(0.55, 0.22, 0.06, 0.22 * fade)); 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))), SMOKE => draw_sphere(at, p.size, None, Color::new(c.r, c.g, c.b, 0.14 * (1.0 - life))),
FLASH => { FLASH => {
let r = p.size * (0.3 + life * 0.9); 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 * 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.35 * (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))), RING => ring(at, p.size * (0.2 + life), Color::new(c.r, c.g, c.b, 0.8 * (1.0 - life))),
_ => {} _ => {}