Tile outlines on floors, wall tops and wall corners (toggle with G)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-02 22:45:36 +01:00
1 parent 220d9543cc
commit a101f6ddc4
4 files changed
+75 -6

No files matched your search

+3
View File
@@ -101,6 +101,9 @@ async fn main() {
if (is_key_pressed(KeyCode::P) || is_key_pressed(KeyCode::Space)) && game.state == 0 {
game.paused = !game.paused;
}
if is_key_pressed(KeyCode::G) {
r3d.outlines = !r3d.outlines;
}
if is_key_pressed(KeyCode::M) {
sounds.muted = !sounds.muted;
}
+70 -5
View File
@@ -319,6 +319,25 @@ impl MB {
);
}
}
// Thin strip lying on a surface (normal n) from p to q, used for tile outlines
fn strip(&mut self, p: Vec3, q: Vec3, n: Vec3, w: f32, c: [u8; 4]) {
let side = (q - p).cross(n).normalize_or_zero() * (w * 0.5);
let o = n * 0.006;
self.quad([p - side + o, q - side + o, q + side + o, p + side + o], [Vec2::ZERO; 4], [c; 4], [n; 4]);
}
// Polyline along displaced points
fn polyline(&mut self, pts: &[Vec3], n: Vec3, w: f32, c: [u8; 4]) {
for k in 0..pts.len().saturating_sub(1) {
self.strip(pts[k], pts[k + 1], n, w, c);
}
}
fn into_plain_mesh(self) -> Option<Mesh> {
if self.v.is_empty() {
None
} else {
Some(Mesh { vertices: self.v, indices: self.i, texture: None })
}
}
fn into_mesh(self, tex: &Texture2D) -> Option<Mesh> {
if self.v.is_empty() {
None
@@ -379,9 +398,23 @@ fn solid_at(map: &Map, x: i32, z: i32) -> bool {
map.get(x, z).map_or(true, |t| t.solid())
}
fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas: &Texture2D) -> (Option<Mesh>, Option<Mesh>) {
const OUTLINE_W: f32 = 0.035;
const OUTLINE_C: [u8; 4] = [8, 5, 6, 150];
// Displaced points along a straight edge (same displacement as the wall grids -> lines follow the surface)
fn edge_pts(a: Vec3, b: Vec3, n: usize) -> Vec<Vec3> {
(0..=n)
.map(|k| {
let p = a.lerp(b, k as f32 / n as f32);
p + displace(p)
})
.collect()
}
fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas: &Texture2D) -> (Option<Mesh>, Option<Mesh>, Option<Mesh>) {
let mut mb = MB::new();
let mut ov = MB::new();
let mut ln = MB::new();
let ld = LIGHT_DIR.normalize();
for z in cz * CHUNK..(cz + 1) * CHUNK {
for x in cx * CHUNK..(cx + 1) * CHUNK {
@@ -406,6 +439,13 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
c,
[Vec3::Y; 4],
);
// outlines: north and west edge, only between two open tiles (each edge once)
if !solid_at(map, x, z - 1) {
ln.strip(vec3(xf, 0.0, zf), vec3(xf + 1.0, 0.0, zf), Vec3::Y, OUTLINE_W, OUTLINE_C);
}
if !solid_at(map, x - 1, z) {
ln.strip(vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0), Vec3::Y, OUTLINE_W, OUTLINE_C);
}
// room decor
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), 1.0);
match t.kind {
@@ -455,6 +495,19 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
(ua, ub),
|_p, nrm| Vec3::splat(top_amb * (0.75 + 0.25 * nrm.dot(ld).max(0.0))),
);
// outlines on the wall top: north/west always, south/east where no wall continues
let tl = vec3(xf, WALL_H, zf);
let tr = vec3(xf + 1.0, WALL_H, zf);
let br = vec3(xf + 1.0, WALL_H, zf + 1.0);
let bl = vec3(xf, WALL_H, zf + 1.0);
ln.polyline(&edge_pts(tl, tr, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
ln.polyline(&edge_pts(tl, bl, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
if !solid_at(map, x, z + 1) {
ln.polyline(&edge_pts(bl, br, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
}
if !solid_at(map, x + 1, z) {
ln.polyline(&edge_pts(tr, br, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
}
if t.marked {
let (a, b) = cell_uv(T_MARK);
let y = WALL_H + 0.12;
@@ -478,6 +531,10 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
_ => (vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0)), // west
};
let out = vec3(dx as f32, 0.0, dz as f32);
// vertical outlines at both ends of the exposed face (tile borders and corners)
for end in [a, b] {
ln.polyline(&edge_pts(end, end + vec3(0.0, WALL_H, 0.0), n), out, OUTLINE_W, OUTLINE_C);
}
build_grid(
&mut mb,
|u, v| a + (b - a) * u + vec3(0.0, WALL_H * (1.0 - v), 0.0),
@@ -492,7 +549,7 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
}
}
}
(mb.into_mesh(atlas), ov.into_mesh(atlas))
(mb.into_mesh(atlas), ov.into_mesh(atlas), ln.into_plain_mesh())
}
// Subdivided, displaced face. pos(u, v) gives the undisplaced point (u, v in 0..1).
@@ -542,6 +599,7 @@ struct Chunk {
sig: u64,
mesh: Option<Mesh>,
overlay: Option<Mesh>,
lines: Option<Mesh>,
}
pub struct Overlay {
@@ -556,6 +614,7 @@ pub struct R3d {
lit: Material,
glow: Material,
chunks: Vec<Chunk>,
pub outlines: bool, // tile outlines on/off
}
fn rgba(u: u32) -> Color {
@@ -585,8 +644,8 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32, fill_bucket: u8) -> u64 {
impl R3d {
pub fn new() -> R3d {
let chunks = (0..NCH * NCH).map(|_| Chunk { sig: 0, mesh: None, overlay: None }).collect();
R3d { lit: lit_material(), glow: glow_material(), chunks }
let chunks = (0..NCH * NCH).map(|_| Chunk { sig: 0, mesh: None, overlay: None, lines: None }).collect();
R3d { lit: lit_material(), glow: glow_material(), chunks, outlines: true }
}
fn update_chunks(&mut self, g: &Game, gfx: &Gfx) {
@@ -599,9 +658,10 @@ impl R3d {
let ch = &mut self.chunks[(cz * NCH + cx) as usize];
if ch.sig != sig {
let a = amb.get_or_insert_with(|| ambient_field(&g.map));
let (m, o) = build_chunk(&g.map, a, cx, cz, bucket as f32 / 6.0, &gfx.atlas);
let (m, o, l) = build_chunk(&g.map, a, cx, cz, bucket as f32 / 6.0, &gfx.atlas);
ch.mesh = m;
ch.overlay = o;
ch.lines = l;
ch.sig = sig;
}
}
@@ -725,6 +785,11 @@ impl R3d {
// ---- unlit glow / overlays
gl_use_material(&self.glow);
for c in &self.chunks {
if self.outlines {
if let Some(l) = &c.lines {
draw_mesh(l);
}
}
if let Some(o) = &c.overlay {
draw_mesh(o);
}
+1 -1
View File
@@ -424,7 +424,7 @@ impl Ui {
("Mausrad Zoomen", TXT),
("1-9, 0, X, V Werkzeug wählen", TXT),
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
("H Diese Hilfe", TXT),
("G Kachel-Umrisse an/aus H Diese Hilfe", TXT),
("", TXT),
("1. Grabe zum Portal und lass es von Grubenwichten erobern.", TXT),
("2. Baue Quartier, Pilzgarten und Schatzkammer.", TXT),