tmx/
tile_type.rs

1use super::*;
2
3/// Tiled has three different rendering types: orthographic, isometric and hexagonal. They are represented through this enum.
4#[derive(Debug, Clone, Copy)]
5pub enum TileType {
6    /// Orthographic (square/rectangle) rendering mode
7    Ortho {
8        /// Width in pixels of a single tile
9        width: u32,
10        /// Height in pixels of a single tile
11        height: u32,
12        /// RenderOrder of tiles. Todo.
13        render_order: RenderOrder,
14    },
15    /// Isometric rendering mode
16    Isometric {
17        /// Width in pixels at the widest point in a single tile
18        width: u32,
19        /// Height in pixels at the tallest point in a single tile
20        height: u32,
21        /// Whether to render in a _staggered_ mode
22        stagger: bool,
23        /// When rendering staggered, whether odd or even columns/rows are shorter.
24        stagger_odd: bool,
25        /// When rendering staggered, whether to stagger the x or y axis.
26        stagger_y: bool,
27        /// RenderOrder of tiles. Todo.
28        render_order: RenderOrder,
29    },
30    /// Hexagonal rendering mode
31    Hexagonal {
32        /// Width in pixels at the widest point in a single tile
33        width: u32,
34        /// Height in pixels at the tallest point in a single tile
35        height: u32,
36        /// Whether odd or even columns/rows are shorter.
37        stagger_odd: bool,
38        /// Whether to stagger the x or y axis.
39        stagger_y: bool,
40        /// Width or height in pixels at the flat side of a hex tile, depending on `stagger_y`.  
41        side_length: u32,
42        /// RenderOrder of tiles. Todo.
43        render_order: RenderOrder,
44    },
45}
46
47impl TileType {
48    /// Convert tile coordinates to it's top left coordinates in pixels. Returns (x, y) in pixels.
49    ///
50    /// * `layer_height` - The height in tiles of the layer that the coordinates belong to. Ignored for non isometric layouts.
51    /// * `x` - The horizontal component of the coordinate
52    /// * `y` - The vertical component of the coordinate
53    pub fn coord_to_pos(&self, layer_height: i32, x: i32, y: i32) -> (i32, i32) {
54        match *self {
55            TileType::Ortho { width, height, .. } => (x * width as i32, y * height as i32),
56
57            TileType::Isometric {
58                width,
59                height,
60                stagger,
61                stagger_odd,
62                stagger_y,
63                ..
64            } => {
65                if stagger {
66                    if stagger_y {
67                        let rx = if (y % 2 == 1) == stagger_odd {
68                            x * width as i32 + width as i32 / 2
69                        } else {
70                            x * width as i32
71                        };
72                        let ry = (height as i32 * y) / 2;
73                        (rx, ry)
74                    } else {
75                        let rx = (width as i32 * x) / 2;
76                        let ry = if (x % 2 == 1) == stagger_odd {
77                            y * height as i32 + height as i32 / 2
78                        } else {
79                            y * height as i32
80                        };
81                        (rx, ry)
82                    }
83                } else {
84                    let rx = (width as i32 * x + width as i32 * (layer_height - 1 - y)) / 2;
85                    let ry = (height as i32 * x + height as i32 * y) / 2;
86                    (rx, ry)
87                }
88            }
89
90            TileType::Hexagonal {
91                width,
92                height,
93                stagger_odd,
94                stagger_y,
95                side_length,
96                ..
97            } => {
98                if stagger_y {
99                    let rx = if (y % 2 == 1) == stagger_odd {
100                        x * width as i32 + width as i32 / 2
101                    } else {
102                        x * width as i32
103                    };
104                    let ry = ((height + side_length) / 2 - 1) as i32 * y;
105                    (rx, ry)
106                } else {
107                    let rx = ((width + side_length) / 2 - 1) as i32 * x;
108                    let ry = if (x % 2 == 1) == stagger_odd {
109                        y * height as i32 + height as i32 / 2
110                    } else {
111                        y * height as i32
112                    };
113                    (rx, ry)
114                }
115            }
116        }
117    }
118
119    /// Convert coordinates in pixels to tile coordinates. Returns (x, y) in tile coordinates.
120    ///
121    /// * `layer_height` - The height in tiles of the layer that the coordinates belong to. Ignored for non isometric layouts.
122    /// * `x` - The horizontal pixel coordinate
123    /// * `y` - The vertical pixel coordinate
124    pub fn pos_to_coord(&self, layer_height: i32, x: i32, y: i32) -> (i32, i32) {
125        match *self {
126            TileType::Ortho { width, height, .. } => (x / width as i32, y / height as i32),
127
128            TileType::Isometric {
129                width,
130                height,
131                stagger,
132                stagger_odd,
133                stagger_y,
134                ..
135            } => {
136                if stagger {
137                    let half_w = width as i32 / 2;
138                    let half_h = height as i32 / 2;
139
140                    let (x, y, off_x, off_y) = match (stagger_odd, stagger_y) {
141                        (true, _) => (x, y, 0, 0),
142                        (false, false) => (x - half_w, y, 1, 0),
143                        (false, true) => (x, y - half_h, 0, 1),
144                    };
145
146                    let ref_x = div2(x, width as i32);
147                    let ref_y = div2(y, height as i32);
148                    let rel_x = x - ref_x * width as i32;
149                    let rel_y = y - ref_y * height as i32;
150
151                    let offset = if rel_y < half_h {
152                        (half_h - rel_y % half_h) * half_w / half_h
153                    } else {
154                        (rel_y % half_h) * half_w / half_h
155                    };
156
157                    let top = rel_y < half_h;
158                    let left = rel_x < offset;
159                    let right = rel_x > width as i32 - offset;
160
161                    let (x, y) = match (stagger_y, top, left, right) {
162                        (true, true, true, false) => (ref_x - 1, ref_y * 2 - 1),
163                        (true, true, false, true) => (ref_x, ref_y * 2 - 1),
164                        (true, false, true, false) => (ref_x - 1, ref_y * 2 + 1),
165                        (true, false, false, true) => (ref_x, ref_y * 2 + 1),
166                        (true, _, _, _) => (ref_x, ref_y * 2),
167
168                        (false, true, true, false) => (ref_x * 2 - 1, ref_y - 1),
169                        (false, true, false, true) => (ref_x * 2 + 1, ref_y - 1),
170                        (false, false, true, false) => (ref_x * 2 - 1, ref_y),
171                        (false, false, false, true) => (ref_x * 2 + 1, ref_y),
172                        (false, _, _, _) => (ref_x * 2, ref_y),
173                    };
174
175                    (x + off_x, y + off_y)
176                } else {
177                    let origin = (width as i32 * layer_height) / 2;
178                    let x = x - origin;
179                    let rx = y / (height as i32) + x / (width as i32);
180                    let ry = y / (height as i32) - x / (width as i32);
181                    (rx, ry)
182                }
183            }
184
185            TileType::Hexagonal {
186                width,
187                height,
188                stagger_odd,
189                stagger_y,
190                side_length,
191                ..
192            } => {
193                if stagger_y {
194                    let col_w = width as i32;
195                    let row_h = (height as i32 - side_length as i32) / 2 + side_length as i32 - 1;
196                    let half_w = width as i32 / 2;
197                    let half_h = height as i32 / 2;
198
199                    let ref_x = div2(x, col_w);
200                    let ref_y = div2(y, row_h);
201                    let rel_x = x - ref_x * col_w;
202                    let rel_y = y - ref_y * row_h;
203
204                    let centers = if (mod2(ref_y, 2) == 1) == stagger_odd {
205                        [
206                            (half_w, -row_h + half_h, ref_x, ref_y - 1),
207                            (0, half_h, ref_x - 1, ref_y),
208                            (col_w, half_h, ref_x, ref_y),
209                        ]
210                    } else {
211                        [
212                            (half_w, half_h, ref_x, ref_y),
213                            (0, -row_h + half_h, ref_x - 1, ref_y - 1),
214                            (col_w, -row_h + half_h, ref_x, ref_y - 1),
215                        ]
216                    };
217
218                    // find nearest center
219                    centers
220                        .iter()
221                        .min_by_key(|&(x, y, _, _)| {
222                            (x - rel_x) * (x - rel_x) + (y - rel_y) * (y - rel_y)
223                        })
224                        .map(|&(_, _, x, y)| (x, y))
225                        .unwrap()
226                } else {
227                    let col_w = (width as i32 - side_length as i32) / 2 + side_length as i32 - 1;
228                    let row_h = height as i32;
229                    let half_w = width as i32 / 2;
230                    let half_h = height as i32 / 2;
231
232                    let ref_x = div2(x, col_w);
233                    let ref_y = div2(y, row_h);
234                    let rel_x = x - ref_x * col_w;
235                    let rel_y = y - ref_y * row_h;
236
237                    let centers = if (mod2(ref_x, 2) == 1) == stagger_odd {
238                        [
239                            (-col_w + half_w, half_h, ref_x - 1, ref_y),
240                            (half_w, 0, ref_x, ref_y - 1),
241                            (half_w, row_h, ref_x, ref_y),
242                        ]
243                    } else {
244                        [
245                            (half_w, half_h, ref_x, ref_y),
246                            (-col_w + half_w, 0, ref_x - 1, ref_y - 1),
247                            (-col_w + half_w, row_h, ref_x - 1, ref_y),
248                        ]
249                    };
250
251                    // find nearest center
252                    centers
253                        .iter()
254                        .min_by_key(|&(x, y, _, _)| {
255                            (x - rel_x) * (x - rel_x) + (y - rel_y) * (y - rel_y)
256                        })
257                        .map(|&(_, _, x, y)| (x, y))
258                        .unwrap()
259                }
260            }
261        }
262    }
263
264    /// Get the tile width of this tile type.
265    pub fn tile_width(&self) -> u32 {
266        match *self {
267            TileType::Ortho { width, .. } => width,
268            TileType::Isometric { width, .. } => width,
269            TileType::Hexagonal { width, .. } => width,
270        }
271    }
272
273    /// Get the tile height of this tile type.
274    pub fn tile_height(&self) -> u32 {
275        match *self {
276            TileType::Ortho { height, .. } => height,
277            TileType::Isometric { height, .. } => height,
278            TileType::Hexagonal { height, .. } => height,
279        }
280    }
281}
282
283fn mod2(x: i32, m: i32) -> i32 {
284    let y = x % m;
285    if y >= 0 {
286        y
287    } else {
288        m + y
289    }
290}
291
292fn div2(x: i32, d: i32) -> i32 {
293    if x >= 0 {
294        x / d
295    } else {
296        x / d - 1
297    }
298}