1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use bevy::{
math::{vec2, vec3, Vec3Swizzles},
prelude::*,
render::render_resource::{AsBindGroup, ShaderType},
};
use super::prelude::*;
#[derive(ShaderType, Clone, Debug, Reflect, AsBindGroup)]
pub struct MapUniform {
/// Size of the map, in tiles.
/// Will be derived from underlying map texture.
pub(crate) map_size: UVec2,
/// Size of the tile atlas, in pixels.
/// Will be derived from the tile atlas texture.
pub(crate) atlas_size: Vec2,
/// Size of each tile, in pixels.
pub(crate) tile_size: Vec2,
pub(crate) atlas_tile_size_factor: i32,
/// Padding between tiles in atlas.
pub(crate) inner_padding: Vec2,
/// Padding at atlas top/left and bottom/right
pub(crate) outer_padding_topleft: Vec2,
pub(crate) outer_padding_bottomright: Vec2,
/// Relative anchor point position in a tile (in [0..1]^2)
pub(crate) tile_anchor_point: Vec2,
/// fractional 2d map index -> projected 2d "map index"
pub(crate) projection: Mat3,
pub(crate) global_transform_matrix: Mat3,
pub(crate) global_transform_translation: Vec3,
/// (derived) Size of the map in world units necessary to display
/// all tiles according to projection.
pub(crate) world_size: Vec2,
/// (derived) Offset of the projected map in world coordinates
pub(crate) world_offset: Vec2,
/// (derived)
pub(crate) n_tiles: UVec2,
/// (derived) local world pos -> fractional 2d map index
///
/// Note that the main use case for the inverse is to transform 2d world coordinates
/// (eg from mouse cursor) to 2d map coordinates with some assumption about how we choose the z
/// coordinate.
/// An inverse of the 3d projection matrix here would assume that you feed in the correct z
/// coordinate and otherwise give wrong results, hence we only invert the 2d part
/// and let the caller handle management of z.
pub(crate) inverse_projection: Mat2,
/// (derived) global world pos -> fractional 2d map index
pub(crate) global_inverse_transform_matrix: Mat3,
pub(crate) global_inverse_transform_translation: Vec3,
}
impl Default for MapUniform {
fn default() -> Self {
Self {
map_size: default(),
atlas_size: default(),
tile_size: default(),
atlas_tile_size_factor: 1,
inner_padding: default(),
outer_padding_topleft: default(),
outer_padding_bottomright: default(),
tile_anchor_point: IDENTITY.tile_anchor_point,
projection: IDENTITY.projection,
global_transform_matrix: default(),
global_transform_translation: default(),
world_size: default(),
world_offset: default(),
n_tiles: default(),
inverse_projection: default(),
global_inverse_transform_matrix: default(),
global_inverse_transform_translation: default(),
}
}
}
impl MapUniform {
pub(crate) fn map_size(&self) -> UVec2 {
self.map_size
}
pub(crate) fn world_size(&self) -> Vec2 {
self.world_size
}
pub(crate) fn map_to_local(&self, map_position: Vec3) -> Vec3 {
(self.projection * map_position) * self.tile_size.extend(1.0)
+ self.world_offset.extend(0.0)
}
pub(crate) fn map_to_world(&self, map_position: Vec3) -> Vec3 {
let local = self.map_to_local(map_position);
self.global_transform_matrix * local + self.global_inverse_transform_translation
}
/// As of now, this will ignore `world`s z coordinate
/// and always project to z=0 on the map.
/// This behavior might change in the future
pub(crate) fn local_to_map(&self, local: Vec3) -> Vec3 {
(self.inverse_projection * ((local.xy() - self.world_offset) / self.tile_size)).extend(0.0)
}
pub(crate) fn world_to_map(&self, world: Vec3) -> Vec3 {
let local = self.global_inverse_transform_matrix * world
+ self.global_inverse_transform_translation;
self.local_to_map(local)
}
pub(crate) fn update_world_size(&mut self) {
// World Size
//
// Determine the bounding rectangle of the projected map (in order to construct the quad
// that will hold the texture).
//
// There is probably a more elegant way to do this, but this
// works and is simple enough:
// 1. save coordinates for all 4 corners
// 2. take maximum x- and y distances
let mut low = self.map_to_local(vec3(0.0, 0.0, 0.0)).xy();
let mut high = low;
for corner in [
vec2(self.map_size().x as f32, 0.0),
vec2(0.0, self.map_size().y as f32),
vec2(self.map_size().x as f32, self.map_size().y as f32),
] {
let pos = self.map_to_local(corner.extend(0.0)).xy();
low = low.min(pos);
high = high.max(pos);
}
self.world_size = high - low;
// Leave a full tile space on each side so overhangs are not visually cut off
let padding = 2.0 * self.tile_size;
// Increase world size by one tile total such that overhangs are fully visible
self.world_size += padding;
// World offset
//
// `map.projection` keeps the map coordinate (0, 0) at the world coordinate (0, 0).
// However after projection we may want the (0, 0) tile to map to a different position than
// say the top left corner (eg for an iso projection it might be vertically centered).
// We use `low` from above to figure out how to correctly translate here.
self.world_offset = vec2(-0.5, -0.5) * self.world_size - low + padding / 2.0;
}
/// Return true iff this update made the uniform ready
/// (ie. it was not ready before and is ready now).
pub(crate) fn update_atlas_size(&mut self, atlas_size: Vec2, force_n_tiles: Option<UVec2>) -> bool {
if self.atlas_size == atlas_size {
return false;
}
self.atlas_size = atlas_size;
match force_n_tiles {
Some(n_tiles) => self.n_tiles = n_tiles,
None => self.update_n_tiles(),
}
true
}
pub(crate) fn _apply_transform(&mut self, transform: GlobalTransform) {
let affine = transform.compute_transform().compute_affine();
self.global_transform_matrix = affine.matrix3.into();
self.global_transform_translation = affine.translation.into();
let inverse = affine.inverse();
self.global_inverse_transform_matrix = inverse.matrix3.into();
self.global_inverse_transform_translation = inverse.translation.into();
}
fn update_n_tiles(&mut self) {
// area after removing outer padding
let inner = self.atlas_size - self.outer_padding_topleft - self.outer_padding_bottomright;
let n_tiles = (inner + self.inner_padding)
/ (self.inner_padding + self.tile_size * self.atlas_tile_size_factor as f32);
let eps = 0.01;
if (n_tiles.x - n_tiles.x.round()).abs() > eps
|| (n_tiles.y - n_tiles.y.round()).abs() > eps
{
warn!(
"Expected an integral number of tiles in your atlas, but computes to be {:?}, use `.with_n_tiles()` in MapBuilder if that is intentional.",
n_tiles
);
}
self.n_tiles = n_tiles.as_uvec2();
}
}