mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
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:
2 files changed
+96
-10
No files matched your search
@@ -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],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user