1use crate::{EdgeDirection, Hex, HexOrientation, VertexDirection, orientation::SQRT_3};
2use glam::Vec2;
3
4#[derive(Debug, Clone)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
61#[cfg_attr(
62 feature = "bevy_ecs",
63 derive(bevy_ecs::resource::Resource, bevy_ecs::component::Component)
64)]
65pub struct HexLayout {
66 pub orientation: HexOrientation,
68 pub origin: Vec2,
71 pub scale: Vec2,
74}
75
76impl HexLayout {
77 pub fn invert_x(&mut self) {
79 self.scale.x *= -1.0;
80 }
81
82 pub fn invert_y(&mut self) {
84 self.scale.y *= -1.0;
85 }
86
87 #[must_use]
90 #[inline]
91 pub fn transform_vector(&self, vector: Vec2) -> Vec2 {
92 vector * self.scale
93 }
94
95 #[must_use]
98 #[inline]
99 pub fn transform_point(&self, point: Vec2) -> Vec2 {
100 self.origin + self.transform_vector(point)
101 }
102
103 #[must_use]
106 #[inline]
107 pub fn inverse_transform_vector(&self, vector: Vec2) -> Vec2 {
108 vector / self.scale
109 }
110
111 #[must_use]
114 #[inline]
115 pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 {
116 self.inverse_transform_vector(point - self.origin)
117 }
118}
119
120impl HexLayout {
121 #[must_use]
122 #[inline]
123 pub fn hex_to_world_pos(&self, hex: Hex) -> Vec2 {
125 self.hex_to_center_aligned_world_pos(hex) + self.origin
126 }
127
128 #[must_use]
129 #[inline]
130 pub(crate) fn hex_to_center_aligned_world_pos(&self, hex: Hex) -> Vec2 {
133 let p = self.orientation.forward(hex.as_vec2());
134 self.transform_vector(p)
135 }
136
137 #[must_use]
138 #[inline]
139 pub fn fract_hex_to_world_pos(&self, hex: Vec2) -> Vec2 {
142 let p = self.orientation.forward(hex);
143 self.transform_point(p)
144 }
145
146 #[must_use]
147 #[inline]
148 pub fn world_pos_to_hex(&self, pos: Vec2) -> Hex {
150 let p = self.world_pos_to_fract_hex(pos).to_array();
151 Hex::round(p)
152 }
153
154 #[must_use]
155 pub fn world_pos_to_fract_hex(&self, pos: Vec2) -> Vec2 {
158 let point = self.inverse_transform_point(pos);
159 self.orientation.inverse(point)
160 }
161
162 #[must_use]
163 pub fn hex_corners(&self, hex: Hex) -> [Vec2; 6] {
166 let center = self.hex_to_world_pos(hex);
167 self.center_aligned_hex_corners().map(|c| c + center)
168 }
169
170 #[must_use]
173 pub fn hex_edge_corners(&self, hex: Hex) -> [[Vec2; 2]; 6] {
174 let center = self.hex_to_world_pos(hex);
175 self.center_aligned_edge_corners()
176 .map(|p| p.map(|c| c + center))
177 }
178
179 #[must_use]
180 pub fn center_aligned_hex_corners(&self) -> [Vec2; 6] {
183 VertexDirection::ALL_DIRECTIONS.map(|dir| dir.world_unit_vector(self))
184 }
185
186 #[must_use]
187 pub(crate) fn center_aligned_edge_corners(&self) -> [[Vec2; 2]; 6] {
189 EdgeDirection::ALL_DIRECTIONS
190 .map(|dir| dir.vertex_directions().map(|v| v.world_unit_vector(self)))
191 }
192
193 #[inline]
194 #[must_use]
195 pub fn rect_size(&self) -> Vec2 {
198 const FLAT_RECT: Vec2 = Vec2::new(2.0, SQRT_3);
199 const POINTY_RECT: Vec2 = Vec2::new(SQRT_3, 2.0);
200
201 self.scale
202 * match self.orientation {
203 HexOrientation::Pointy => POINTY_RECT,
204 HexOrientation::Flat => FLAT_RECT,
205 }
206 }
207}
208
209#[cfg(feature = "grid")]
210impl HexLayout {
211 #[must_use]
214 pub fn edge_coordinates(&self, edge: crate::GridEdge) -> [Vec2; 2] {
215 let origin = self.hex_to_world_pos(edge.origin);
216 edge.vertices()
217 .map(|v| self.__vertex_coordinates(v) + origin)
218 }
219
220 #[must_use]
223 pub fn all_edge_coordinates(&self, coord: Hex) -> [[Vec2; 2]; 6] {
224 let origin = self.hex_to_world_pos(coord);
225 coord.all_edges().map(|edge| {
226 edge.vertices()
227 .map(|v| self.__vertex_coordinates(v) + origin)
228 })
229 }
230
231 #[must_use]
233 pub fn vertex_coordinates(&self, vertex: crate::GridVertex) -> Vec2 {
234 let origin = self.hex_to_world_pos(vertex.origin);
235 self.__vertex_coordinates(vertex) + origin
236 }
237
238 fn __vertex_coordinates(&self, vertex: crate::GridVertex) -> Vec2 {
239 vertex.direction.world_unit_vector(self)
240 }
241}
242
243impl HexLayout {
245 #[must_use]
246 #[inline]
247 pub const fn new(orientation: HexOrientation) -> Self {
250 Self {
251 orientation,
252 origin: Vec2::ZERO,
253 scale: Vec2::ONE,
254 }
255 }
256
257 #[must_use]
258 #[inline]
259 pub const fn flat() -> Self {
262 Self::new(HexOrientation::Flat)
263 }
264
265 #[must_use]
266 #[inline]
267 pub const fn pointy() -> Self {
270 Self::new(HexOrientation::Pointy)
271 }
272
273 #[must_use]
274 #[inline]
275 pub const fn with_origin(mut self, origin: Vec2) -> Self {
277 self.origin = origin;
278 self
279 }
280
281 #[must_use]
282 #[inline]
283 pub const fn with_hex_size(mut self, size: f32) -> Self {
285 self.scale = Vec2::splat(size);
286 self
287 }
288
289 #[inline]
290 #[must_use]
291 pub fn with_rect_size(self, rect_size: Vec2) -> Self {
295 const FLAT_RECT: Vec2 = Vec2::new(0.5, 1.0 / SQRT_3);
296 const POINTY_RECT: Vec2 = Vec2::new(1.0 / SQRT_3, 0.5);
297
298 let scale = rect_size
299 * match self.orientation {
300 HexOrientation::Pointy => POINTY_RECT,
301 HexOrientation::Flat => FLAT_RECT,
302 };
303 self.with_scale(scale)
304 }
305
306 #[must_use]
307 #[inline]
308 pub const fn with_scale(mut self, scale: Vec2) -> Self {
314 self.scale = scale;
315 self
316 }
317}
318
319impl Default for HexLayout {
320 #[inline]
321 fn default() -> Self {
322 Self::new(HexOrientation::default())
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use approx::assert_relative_eq;
329
330 use super::*;
331
332 #[test]
333 fn flat_corners() {
334 let point = Hex::new(0, 0);
335 let mut layout = HexLayout::new(HexOrientation::Flat).with_scale(Vec2::new(10., 10.));
336 let corners = layout.hex_corners(point).map(Vec2::round);
337 assert_eq!(
338 corners,
339 [
340 Vec2::new(10.0, 0.0),
341 Vec2::new(5.0, 9.0),
342 Vec2::new(-5.0, 9.0),
343 Vec2::new(-10.0, 0.0),
344 Vec2::new(-5.0, -9.0),
345 Vec2::new(5.0, -9.0),
346 ]
347 );
348 layout.invert_y();
349 let corners = layout.hex_corners(point).map(Vec2::round);
350 assert_eq!(
351 corners,
352 [
353 Vec2::new(10.0, 0.0),
354 Vec2::new(5.0, -9.0),
355 Vec2::new(-5.0, -9.0),
356 Vec2::new(-10.0, 0.0),
357 Vec2::new(-5.0, 9.0),
358 Vec2::new(5.0, 9.0),
359 ]
360 );
361 }
362
363 #[test]
364 fn pointy_corners() {
365 let point = Hex::new(0, 0);
366 let mut layout = HexLayout::new(HexOrientation::Pointy).with_scale(Vec2::new(10., 10.));
367 let corners = layout.hex_corners(point).map(Vec2::round);
368 assert_eq!(
369 corners,
370 [
371 Vec2::new(9.0, -5.0),
372 Vec2::new(9.0, 5.0),
373 Vec2::new(-0.0, 10.0),
374 Vec2::new(-9.0, 5.0),
375 Vec2::new(-9.0, -5.0),
376 Vec2::new(0.0, -10.0),
377 ]
378 );
379 layout.invert_y();
380 let corners = layout.hex_corners(point).map(Vec2::round);
381 assert_eq!(
382 corners,
383 [
384 Vec2::new(9.0, 5.0),
385 Vec2::new(9.0, -5.0),
386 Vec2::new(-0.0, -10.0),
387 Vec2::new(-9.0, -5.0),
388 Vec2::new(-9.0, 5.0),
389 Vec2::new(0.0, 10.0),
390 ]
391 );
392 }
393
394 #[test]
395 fn rect_size() {
396 let sizes = [
397 Vec2::ZERO,
398 Vec2::ONE,
399 Vec2::X,
400 Vec2::Y,
401 Vec2::NEG_ONE,
402 Vec2::NEG_X,
403 Vec2::NEG_Y,
404 Vec2::new(10.0, 5.0),
405 Vec2::new(-10.0, 31.1),
406 Vec2::new(110.0, 25.0),
407 Vec2::new(-210.54, -54.0),
408 ];
409 for size in sizes {
410 for orientation in [HexOrientation::Flat, HexOrientation::Pointy] {
411 let layout = HexLayout::new(orientation).with_rect_size(size);
412 let rect = layout.rect_size();
413 assert_relative_eq!(rect.x, size.x, epsilon = 0.00001);
414 assert_relative_eq!(rect.y, size.y, epsilon = 0.00001);
415 }
416 }
417 }
418}