Draw map markers on the cartography table's preview

MapRenderer.render in a screen draws the map's decorations over its image,
all of them since the map isn't in an item frame.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Rx5M6NS763D2WVnb2HmcTH
This commit is contained in:
Claude committed 2026-10-30 18:42:28 +00:00
1 parent 1c7eb12800
commit 72f66bb71c
2 files changed
+96 -10

No files matched your search

+84 -2
View File
@@ -116,12 +116,13 @@ impl Game<'_> {
);
};
let preview = |batch: &mut HudBatch, game: &mut Self, x: f32, y: f32, scale: f32| {
if let Some(rect) = id
if let Some((id, rect)) = id
.filter(|_| state.is_some())
.and_then(|id| game.map_texture(id))
.and_then(|id| game.map_texture(id).map(|rect| (id, rect)))
{
let size = 128.0 * scale;
batch.rect_uv(ox + x, oy + y, size, size, rect, u32::MAX);
game.draw_map_decorations(batch, id, ox + x, oy + y, scale);
}
};
if scaling && !locked {
@@ -142,6 +143,40 @@ impl Game<'_> {
}
}
}
impl Game<'_> {
/// `MapRenderer.render` in a screen (`GuiGraphicsExtractor.map`): map
/// `id`'s decorations, all of them (not in an item frame), over its
/// image at `(x, y)` drawn `scale` GUI pixels per map pixel.
fn draw_map_decorations(&self, batch: &mut HudBatch, id: i32, x: f32, y: f32, scale: f32) {
let Some(map) = self.maps.data.get(&id) else {
return;
};
for d in &map.decorations {
let Some(s) = self.gui.atlas.sprites.get(d.sprite).copied() else {
continue;
};
batch.quad(
decoration_corners(d, x, y, scale),
[[s.u0, s.v0], [s.u1, s.v0], [s.u1, s.v1], [s.u0, s.v1]],
u32::MAX,
);
}
}
}
/// Decoration `d`'s corners on screen, for a map drawn at `(x, y)` with
/// `scale` GUI pixels per map pixel: (-1, 1), (1, 1), (1, -1), (-1, -1)
/// under `MapRenderer.render`'s pose.
fn decoration_corners(d: &super::maps::Decoration, x: f32, y: f32, scale: f32) -> [[f32; 2]; 4] {
let m = glam::Mat4::from_translation(glam::Vec3::new(x, y, 0.0))
* glam::Mat4::from_scale(glam::Vec3::new(scale, scale, 1.0))
* super::maps::decoration_pose(d, 0.0);
[(-1.0, 1.0), (1.0, 1.0), (1.0, -1.0), (-1.0, -1.0)].map(|(cx, cy)| {
let p = m.transform_point3(glam::Vec3::new(cx, cy, 0.0));
[p.x, p.y]
})
}
impl MapData {
/// `MapItemSavedData.scaled`: a fresh map one scale up around the same centre.
pub(super) fn scaled(&self) -> MapData {
@@ -172,3 +207,50 @@ impl MapData {
m
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::game::maps::Decoration;
fn close(a: [[f32; 2]; 4], b: [[f32; 2]; 4]) {
for (a, b) in a.iter().zip(&b) {
assert!(
(a[0] - b[0]).abs() < 1e-4 && (a[1] - b[1]).abs() < 1e-4,
"{a:?} {b:?}"
);
}
}
#[test]
fn map_markers_sit_on_the_preview_like_on_a_held_map() {
let player = |x, y, rot| Decoration {
sprite: "map/decorations/player",
x,
y,
rot,
holder: None,
};
// The centre, facing north: 8 map pixels wide, nudged by (-0.5, 0.5).
close(
decoration_corners(&player(0, 0, 0), 0.0, 0.0, 1.0),
[[59.5, 68.5], [67.5, 68.5], [67.5, 60.5], [59.5, 60.5]],
);
// A quarter turn (rotation 4 of 16) turns the quad about its marker.
close(
decoration_corners(&player(0, 0, 4), 0.0, 0.0, 1.0),
[[59.5, 59.5], [59.5, 67.5], [67.5, 67.5], [67.5, 59.5]],
);
// The preview's offset and scale apply after: the top-left corner
// marker on the 0.45 preview at (71, 17).
close(
decoration_corners(&player(-128, -128, 0), 71.0, 17.0, 0.45),
[
[71.0 - 4.5 * 0.45, 17.0 + 4.5 * 0.45],
[71.0 + 3.5 * 0.45, 17.0 + 4.5 * 0.45],
[71.0 + 3.5 * 0.45, 17.0 - 3.5 * 0.45],
[71.0 - 4.5 * 0.45, 17.0 - 3.5 * 0.45],
],
);
}
}
+12 -8
View File
@@ -337,6 +337,17 @@ const DECORATION_TYPES: [(&str, &str); 24] = [
("ocean_ruin_warm", "map/decorations/warm_ocean_ruins"),
];
/// `MapDecorationType.showOnItemFrame` is false for the player arrows and the two markers.
/// `MapRenderer.render`'s pose for decoration `d` in map pixels (0..128,
/// y down) at depth `z`: its quad is (-1, -1) to (1, 1) under it.
pub(super) fn decoration_pose(d: &Decoration, z: f32) -> glam::Mat4 {
glam::Mat4::from_translation(glam::Vec3::new(
f32::from(d.x) / 2.0 + 64.0,
f32::from(d.y) / 2.0 + 64.0,
z,
)) * glam::Mat4::from_rotation_z((f32::from(d.rot) * 360.0 / 16.0).to_radians())
* glam::Mat4::from_scale(glam::Vec3::splat(4.0))
* glam::Mat4::from_translation(glam::Vec3::new(-0.125, 0.125, 0.0))
}
fn shown_on_frame(sprite: &str) -> bool {
!matches!(
decoration_type(sprite),
@@ -1277,14 +1288,7 @@ impl Game<'_> {
let Some(s) = self.gui.atlas.sprites.get(d.sprite).copied() else {
continue;
};
let dm = glam::Mat4::from_translation(glam::Vec3::new(
f32::from(d.x) / 2.0 + 64.0,
f32::from(d.y) / 2.0 + 64.0,
-0.02 - count as f32 * 0.001,
)) * glam::Mat4::from_rotation_z(
(f32::from(d.rot) * 360.0 / 16.0).to_radians(),
) * glam::Mat4::from_scale(glam::Vec3::splat(4.0))
* glam::Mat4::from_translation(glam::Vec3::new(-0.125, 0.125, 0.0));
let dm = decoration_pose(d, -0.02 - count as f32 * 0.001);
let p = |x: f32, y: f32| dm.transform_point3(glam::Vec3::new(x, y, 0.0)).to_array();
let mut q = |corners: [[f32; 3]; 4], uv: [[f32; 2]; 4]| {
for (c, uv) in corners.into_iter().zip(uv) {