" ]
5 | description = "Helper library for working with 2d hex-grid maps"
6 | documentation = "https://docs.rs/hex2d/"
7 | repository = "https://github.com/dpc/hex2d-rs"
8 | homepage = "https://github.com/dpc/hex2d-rs"
9 | readme = "README.md"
10 | keywords = ["game", "map", "hex", "hexagonal", "coordinate"]
11 | license = "MIT"
12 |
13 | [lib]
14 | name = "hex2d"
15 | path = "src/lib.rs"
16 |
17 | [features]
18 | default = [ "serde-serde" ]
19 | # Needs a different name due to: https://github.com/rust-lang/cargo/issues/1286
20 | serde-serde = ["serde", "serde_derive"]
21 |
22 | [dependencies]
23 | num = "0.4"
24 |
25 | serde = { version="1.0", optional=true }
26 | serde_derive = { version="1.0", optional=true }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Dawid Ciężarkiewicz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | include Makefile.defs
2 |
3 | default: $(DEFAULT_TARGET)
4 |
5 | .PHONY: run test build doc clean
6 | run test build doc clean:
7 | @cargo $@
8 |
9 | simple:
10 | cargo run --example simple
11 |
12 | .PHONY: docview
13 | docview: doc
14 | xdg-open target/doc/$(PKG_NAME)/index.html
15 |
--------------------------------------------------------------------------------
/Makefile.defs:
--------------------------------------------------------------------------------
1 | PKG_NAME=hex2d
2 | DEFAULT_TARGET=test
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hex2d
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Documentation
13 |
14 |
15 |
16 | ## Introduction
17 |
18 | Library for working with 2d hex map systems.
19 |
20 | A lot of ideas taken from [redbloggames hexagon page][hexagon]
21 |
22 | [hexagon]: http://www.redblobgames.com/grids/hexagons/
23 | [hex2d-rs]: http://github.com/dpc/hex2d-rs
24 | [hex2d-dpcext-rs]: http://github.com/dpc/hex2d-dpcext-rs
25 |
26 | Read [Documentation](https://docs.rs/hex2d) for details.
27 |
28 | See [issues](//github.com/dpc/hex2d-rs/issues/) for TODO and BUGs.
29 |
30 | You might be interested in additional functionality provided by [hex2d-dpcext-rs] library.
31 |
32 | ### Coordinate system
33 |
34 | Pointy-topped:
35 |
36 | /\
37 | / \
38 | | |
39 | | |
40 | \ /
41 | \/
42 |
43 | -z
44 | +y YZ | XZ +x
45 | --- | ---
46 | --- | ---
47 | --- | ---
48 | YX -x- XY
49 | --- | ---
50 | --- | ---
51 | --- ZX | ZY ---
52 | -x | -y
53 | +z
54 |
55 | Flat-topped:
56 |
57 | ____
58 | / \
59 | / \
60 | \ /
61 | \____/
62 |
63 | +y -z
64 | \ /
65 | \ YZ /
66 | YX \ / XZ
67 | \ /
68 | -x--------x--------+x
69 | / \
70 | ZX / \ XY
71 | / ZY \
72 | / \
73 | +z -y
74 |
75 | ## Building
76 |
77 | cargo build
78 |
79 |
80 | ## Verification Recommendation
81 |
82 | To help with the maintaince, the ownership of this crate is potentially shared between multiple developers.
83 | It is recommended to always use [cargo-crev](https://github.com/crev-dev/cargo-crev)
84 | to verify the trustworthiness of each of your dependencies, including this one.
85 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | // See LICENSE file for more information
2 |
3 | //! Hexagonal map operations utility library
4 | //!
5 | //! A lot of ideas taken from [redbloggames hexagon page][hexagon]
6 | //!
7 | //! [hexagon]: http://www.redblobgames.com/grids/hexagons/
8 | //!
9 | //! Pointy-topped:
10 | //!
11 | //! ```text
12 | //! /\
13 | //! / \
14 | //! | |
15 | //! | |
16 | //! \ /
17 | //! \/
18 | //!
19 | //! -z
20 | //! +y YZ | XZ +x
21 | //! --- | ---
22 | //! --- | ---
23 | //! --- | ---
24 | //! YX -x- XY
25 | //! --- | ---
26 | //! --- | ---
27 | //! --- ZX | ZY ---
28 | //! -x | -y
29 | //! +z
30 | //! ```
31 | //!
32 | //! Flat-topped:
33 | //!
34 | //! ```text
35 | //! ____
36 | //! / \
37 | //! / \
38 | //! \ /
39 | //! \____/
40 | //!
41 | //! +y -z
42 | //! \ /
43 | //! \ YZ /
44 | //! YX \ / XZ
45 | //! \ /
46 | //! -x--------x--------+x
47 | //! / \
48 | //! ZX / \ XY
49 | //! / ZY \
50 | //! / \
51 | //! +z -y
52 | //! ```
53 | //!
54 |
55 | // TODO:
56 | // Implement Eq between (i, i) and (i, i, i) by using Into
57 |
58 | #![crate_name = "hex2d"]
59 | #![crate_type = "lib"]
60 |
61 | #![warn(missing_docs)]
62 |
63 | extern crate num;
64 | #[cfg(feature="serde-serde")]
65 | extern crate serde;
66 | #[cfg(feature="serde-serde")]
67 | #[macro_use]
68 | extern crate serde_derive;
69 |
70 | use num::{Float, One, Zero};
71 | use std::ops::{Add, Sub, Neg};
72 | use std::cmp::{max, min};
73 | use std::convert::{Into, From, TryInto};
74 | use std::f64::consts::PI;
75 | use std::iter;
76 |
77 | pub use Direction::*;
78 | pub use Angle::*;
79 | use Spin::*;
80 | use Spacing::*;
81 |
82 |
83 | /// Integer trait required by this library
84 | pub trait Integer : num::Signed +
85 | num::Integer +
86 | num::CheckedAdd +
87 | num::ToPrimitive +
88 | num::FromPrimitive +
89 | num::NumCast +
90 | One + Zero + Copy { }
91 |
92 | impl Integer for I
93 | where
94 | I : num::Signed +
95 | num::Integer +
96 | num::CheckedAdd +
97 | num::ToPrimitive +
98 | num::FromPrimitive +
99 | num::NumCast +
100 | One + Zero + Copy { }
101 |
102 | #[cfg(test)]
103 | mod test;
104 |
105 | /// Coordinate on 2d hexagonal grid
106 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
107 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
108 | pub struct Coordinate {
109 | /// `x` coordinate
110 | pub x : I,
111 | /// `y` coordinate
112 | pub y : I,
113 | }
114 |
115 | /// Position on 2d hexagonal grid (Coordinate + Direction)
116 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
117 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
118 | pub struct Position {
119 | /// `x` coordinate
120 | pub coord : Coordinate,
121 | /// `y` coordinate
122 | pub dir : Direction,
123 | }
124 |
125 | /// Direction on a hexagonal map
126 | ///
127 | /// See `Coordinate` for graph with directions.
128 | ///
129 | /// Naming convention: increasing coordinate for a given direction is first
130 | /// decreasing is second. The missing coordinate is unaffected by a move in
131 | /// a given direction.
132 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
133 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
134 | pub enum Direction {
135 | /// +Y -Z
136 | YZ,
137 | /// -Z +X
138 | XZ,
139 | /// +X -Y
140 | XY,
141 | /// -Y +Z
142 | ZY,
143 | /// +Z -X
144 | ZX,
145 | /// -X +Y
146 | YX,
147 | }
148 |
149 | // Use Direction::all() instead
150 | static ALL_DIRECTIONS : [Direction; 6] = [ YZ, XZ, XY, ZY, ZX, YX ];
151 | static ALL_ANGLES : [Angle; 6] = [ Forward, Right, RightBack, Back, LeftBack, Left];
152 |
153 | /// Angle, relative to a Direction
154 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
155 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
156 | pub enum Angle {
157 | /// 0deg clockwise
158 | Forward,
159 | /// 60deg clockwise
160 | Right,
161 | /// 120deg clockwise
162 | RightBack,
163 | /// 180deg clockwise
164 | Back,
165 | /// 240deg clockwise
166 | LeftBack,
167 | /// 300deg clockwise
168 | Left,
169 | }
170 |
171 | /// Spinning directions
172 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
173 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
174 | pub enum Spin {
175 | /// Clockwise
176 | CW(Direction),
177 | /// Counterclockwise
178 | CCW(Direction),
179 | }
180 |
181 | impl From for Direction {
182 | fn from(spin: Spin) -> Self {
183 | match spin {
184 | CW(d) => d,
185 | CCW(d) => d,
186 | }
187 | }
188 | }
189 |
190 | /// Floating point tile size for pixel conversion functions
191 | #[derive(Copy, Clone, PartialEq, Debug, PartialOrd)]
192 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
193 | pub enum Spacing {
194 | /// Hex-grid with an edge on top
195 | FlatTop(F),
196 | /// Hex-grid with a corner on top
197 | PointyTop(F),
198 | }
199 |
200 | /// Integer pixel tile size for integer pixel conversion functions
201 | ///
202 | /// Example values that give good results:
203 | ///
204 | /// * FlatTop(3, 2)
205 | /// * PointyTop(2, 1)
206 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Ord, PartialOrd)]
207 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
208 | pub enum IntegerSpacing {
209 | /// Hex-grid with an edge on top
210 | FlatTop(I, I),
211 | /// Hex-grid with a corner on top
212 | PointyTop(I, I),
213 | }
214 |
215 | impl Coordinate {
216 | /// Create new Coord from `x` and `y`
217 | pub fn new(x : I, y : I) -> Coordinate {
218 | Coordinate { x: x, y: y}
219 | }
220 |
221 | /// Round x, y float to nearest hex coordinates
222 | pub fn nearest(x : F, y : F) -> Coordinate {
223 | let zero: F = Zero::zero();
224 | let z: F = zero - x - y;
225 |
226 | let mut rx = x.round();
227 | let mut ry = y.round();
228 | let rz = z.round();
229 |
230 | let x_diff = (rx - x).abs();
231 | let y_diff = (ry - y).abs();
232 | let z_diff = (rz - z).abs();
233 |
234 | if x_diff > y_diff && x_diff > z_diff {
235 | rx = -ry - rz;
236 | } else if y_diff > z_diff {
237 | ry = -rx - rz;
238 | } else {
239 | // not needed, kept for a reference
240 | // rz = -rx - ry;
241 | }
242 |
243 | Coordinate {
244 | x: I::from(rx).unwrap(),
245 | y: I::from(ry).unwrap(),
246 | }
247 | }
248 |
249 | /// Round x, y float to nearest hex coordinates
250 | ///
251 | /// Return None, if exactly on the border of two hex coordinates
252 | pub fn nearest_lossy(x : F, y : F) -> Option> {
253 | let zero: F = Zero::zero();
254 | let z: F = zero - x - y;
255 |
256 | let mut rx = x.round();
257 | let mut ry = y.round();
258 | let mut rz = z.round();
259 |
260 | let x_diff = (rx - x).abs();
261 | let y_diff = (ry - y).abs();
262 | let z_diff = (rz - z).abs();
263 |
264 | if x_diff > y_diff && x_diff > z_diff {
265 | rx = -ry - rz;
266 | } else if y_diff > z_diff {
267 | ry = -rx - rz;
268 | } else {
269 | rz = -rx - ry;
270 | }
271 |
272 | let x_diff = (rx - x).abs();
273 | let y_diff = (ry - y).abs();
274 | let z_diff = (rz - z).abs();
275 |
276 | if x_diff + y_diff + z_diff > F::from(0.99).unwrap() {
277 | return None;
278 | }
279 |
280 | Some(Coordinate {
281 | x: I::from(rx).unwrap(),
282 | y: I::from(ry).unwrap(),
283 | })
284 | }
285 |
286 | /// Find the hex containing a pixel. The origin of the pixel coordinates
287 | /// is the center of the hex at (0,0) in hex coordinates.
288 | pub fn from_pixel(x: F, y: F, spacing: Spacing) -> Coordinate {
289 | let f3: F = F::from(3).unwrap();
290 | let f2: F = F::from(2).unwrap();
291 | let f3s: F = f3.sqrt();
292 | match spacing {
293 | Spacing::PointyTop(size) => {
294 | let q = (x * f3s/f3 - y / f3) / size;
295 | let r = y * f2/f3 / size;
296 | return Coordinate::nearest(q, -r -q);
297 | },
298 | Spacing::FlatTop(size) => {
299 | let q = x * f2/f3 / size;
300 | let r = (-x / f3 + f3s/f3 * y) / size;
301 | return Coordinate::nearest(q, -r -q);
302 | }
303 | }
304 | }
305 |
306 | /// Convert integer pixel coordinates `v` using `spacing` to nearest coordinate that has both
307 | /// integer pixel coordinates lower or equal to `v`. Also return offset (in integer pixels)
308 | /// from that coordinate.
309 | ///
310 | /// Useful for ASCII visualization.
311 | // Took me a while to figure this out, but it seems to work. Brilliant.
312 | pub fn nearest_with_offset(spacing : IntegerSpacing, v : (I, I)) -> (Coordinate, (I, I)) {
313 | let (asc_x, asc_y) = v;
314 |
315 | let two : I = num::FromPrimitive::from_i8(2).unwrap();
316 |
317 | let ((q, qo),(r, ro)) = match spacing {
318 | IntegerSpacing::FlatTop(w, h) => (
319 | (asc_x.div_floor(&w), asc_x.mod_floor(&w)),
320 | (
321 | (asc_y - h * asc_x.div_floor(&w) / two).div_floor(&h),
322 | (asc_y + h / two * asc_x.div_floor(&w)).mod_floor(&h)
323 | )
324 | ),
325 | IntegerSpacing::PointyTop(w, h) => (
326 | (
327 | (asc_x - w * asc_y.div_floor(&h) / two).div_floor(&w),
328 | (asc_x + w / two * asc_y.div_floor(&h)).mod_floor(&w)
329 | ),
330 | (asc_y.div_floor(&h), asc_y.mod_floor(&h))
331 | ),
332 | };
333 |
334 | let coord = Coordinate{ x: q, y: -q - r };
335 | (coord, (qo, ro))
336 | }
337 |
338 | /// Convert to pixel coordinates using `spacing`, where the
339 | /// parameter means the edge length of a hexagon.
340 | ///
341 | /// This function is meant for graphical user interfaces
342 | /// where resolution is big enough that floating point calculation
343 | /// make sense.
344 | pub fn to_pixel(&self, spacing : Spacing) -> (F, F) {
345 | let f3: F = F::from(3).unwrap();
346 | let f2: F = F::from(2).unwrap();
347 | let f3s: F = f3.sqrt();
348 | let q: F = F::from(self.x).unwrap();
349 | let r: F = F::from(self.z()).unwrap();
350 | match spacing {
351 | FlatTop(s) => (
352 | s * f3 / f2 * q,
353 | s * f3s * (r + q / f2)
354 | ),
355 | PointyTop(s) => (
356 | s * f3s * (q + r / f2),
357 | s * f3 / f2 * r
358 | )
359 | }
360 | }
361 |
362 | /// Convert to integer pixel coordinates using `spacing`, where the
363 | /// parameters mean the width and height multiplications
364 | pub fn to_pixel_integer(&self, spacing : IntegerSpacing) -> (I, I) {
365 | let q = self.x;
366 | let r = self.z();
367 | let two = num::FromPrimitive::from_i8(2).unwrap();
368 |
369 | match spacing {
370 | IntegerSpacing::FlatTop(w, h) => (
371 | w * q,
372 | h * (r + r + q) / two
373 | ),
374 | IntegerSpacing::PointyTop(w, h) => (
375 | w * (q + q + r) / two,
376 | h * r
377 | )
378 | }
379 | }
380 |
381 | /// Scale coordinate by a factor `s`
382 | pub fn scale(&self, s : I) -> Coordinate {
383 | let x = self.x * s;
384 | let y = self.y * s;
385 | Coordinate{ x: x, y: y }
386 | }
387 |
388 | /// Array with all the neighbors of a coordinate
389 | pub fn neighbors(&self) -> [Coordinate; 6] {
390 | [
391 | *self + YZ,
392 | *self + XZ,
393 | *self + XY,
394 | *self + ZY,
395 | *self + ZX,
396 | *self + YX,
397 | ]
398 | }
399 |
400 | /// Rotate self around a point `(0, 0, 0)` using angle of rotation `a`
401 | pub fn rotate_around_zero(&self, a : Angle) -> Coordinate {
402 |
403 | let (x, y, z) = (self.x, self.y, self.z());
404 |
405 | let (x, y) = match a {
406 | Forward => (x, y),
407 | Right => (-z, -x),
408 | RightBack => (y, z),
409 | Back => (-x, -y),
410 | LeftBack => (z, x),
411 | Left => (-y, -z),
412 | };
413 |
414 | Coordinate{ x: x, y: y}
415 | }
416 |
417 | /// Rotate `self` around a `center` using angle of rotation `a`
418 | pub fn rotate_around(&self, center : Coordinate, a : Angle) -> Coordinate {
419 | let rel_p = *self - center;
420 | let rot_p = rel_p.rotate_around_zero(a);
421 | rot_p + center
422 | }
423 |
424 | fn line_to_iter_gen(&self, dest: Coordinate) -> LineToGen {
425 | let n = self.distance(dest);
426 |
427 | let ax = self.x.to_f32().unwrap();
428 | let ay = self.y.to_f32().unwrap();
429 | let bx = dest.x.to_f32().unwrap();
430 | let by = dest.y.to_f32().unwrap();
431 | LineToGen {
432 | n,
433 | ax,
434 | ay,
435 | bx,
436 | by,
437 |
438 | i: Zero::zero(),
439 | }
440 | }
441 |
442 | /// Iterator over each coordinate in straight line from `self` to `dest`
443 | pub fn line_to_iter(&self, dest: Coordinate) -> LineTo {
444 | LineTo(self.line_to_iter_gen(dest))
445 | }
446 |
447 | /// Iterator over each coordinate in straight line from `self` to `dest`
448 | ///
449 | /// Skip points on the border of two tiles
450 | pub fn line_to_lossy_iter(&self, dest: Coordinate) -> LineToLossy {
451 | LineToLossy(self.line_to_iter_gen(dest))
452 | }
453 |
454 | /// Iterator over each coordinate in straight line from `self` to `dest`
455 | ///
456 | /// On edge condition the pair contains different members, otherwise it's the same.
457 | pub fn line_to_with_edge_detection_iter(&self, dest: Coordinate) -> LineToWithEdgeDetection {
458 | LineToWithEdgeDetection(self.line_to_iter_gen(dest))
459 | }
460 |
461 | /// Z coordinate
462 | pub fn z(&self) -> I
463 | {
464 | -self.x - self.y
465 | }
466 |
467 | /// Direction from center `(0, 0)` to coordinate
468 | ///
469 | /// In case of diagonals (edge of two major directions), prefers direction that is clockwise
470 | /// from the diagonal
471 | ///
472 | /// Returns:
473 | /// None if is center
474 | ///
475 | /// ```
476 | /// use hex2d::{Direction, Coordinate};
477 | /// use hex2d::{Left, Right};
478 | ///
479 | /// let center = Coordinate::new(0, 0);
480 | ///
481 | /// assert_eq!(center.direction_from_center_cw(), None);
482 | ///
483 | /// for &d in Direction::all() {
484 | /// assert_eq!((center + d).direction_from_center_cw(), Some(d));
485 | /// assert_eq!((center + d + (d + Left)).direction_from_center_cw(), Some(d));
486 | /// assert_eq!((center + d + (d + Right)).direction_from_center_cw(), Some(d + Right));
487 | /// }
488 | /// ```
489 | pub fn direction_from_center_cw(&self) -> Option {
490 |
491 | let x = self.x;
492 | let y = self.y;
493 | let z = self.z();
494 | let zero : I = num::FromPrimitive::from_i8(0).unwrap();
495 |
496 | let xy = if z < zero { x >= y } else { x > y };
497 | let yz = if x < zero { y >= z } else { y > z };
498 | let zx = if y < zero { z >= x } else { z > x };
499 | match (xy, yz, zx) {
500 | (true, true, false) => Some(XZ),
501 | (true, false, false) => Some(XY),
502 | (true, false, true) => Some(ZY),
503 |
504 | (false, false, true) => Some(ZX),
505 | (false, true, true) => Some(YX),
506 | (false, true, false) => Some(YZ),
507 | (false, false, false) => None,
508 | (true, true, true) => panic!("You broke math"),
509 | }
510 | }
511 |
512 | /// Directions that lead from center to a given point.
513 | ///
514 | /// Returns an array of one or two directions.
515 | pub fn directions_from_center(&self) -> Vec {
516 | let x = self.x;
517 | let y = self.y;
518 | let z = self.z();
519 | let zero : I = num::FromPrimitive::from_i8(0).unwrap();
520 |
521 | let mut dirs = Vec::with_capacity(2);
522 |
523 | if x > zero && z < zero {
524 | dirs.push(XZ)
525 | }
526 |
527 | if x > zero && y < zero {
528 | dirs.push(XY)
529 | }
530 |
531 | if z > zero && y < zero {
532 | dirs.push(ZY)
533 | }
534 |
535 | if z > zero && x < zero {
536 | dirs.push(ZX)
537 | }
538 |
539 | if y > zero && z < zero {
540 | dirs.push(YZ)
541 | }
542 |
543 | if y > zero && x < zero {
544 | dirs.push(YX)
545 | }
546 |
547 | dirs
548 | }
549 |
550 | /// Direction from center `(0, 0)` to coordinate
551 | ///
552 | /// In case of diagonals (edge of two major directions), prefers direction that is
553 | /// counter-clockwise from the diagonal.
554 | ///
555 | /// Returns:
556 | /// None if is center
557 | ///
558 | /// ```
559 | /// use hex2d::{Direction, Coordinate};
560 | /// use hex2d::{Left, Right};
561 | ///
562 | /// let center = Coordinate::new(0, 0);
563 | ///
564 | /// assert_eq!(center.direction_from_center_ccw(), None);
565 | ///
566 | /// for &d in Direction::all() {
567 | /// assert_eq!((center + d).direction_from_center_ccw(), Some(d));
568 | /// assert_eq!((center + d + (d + Left)).direction_from_center_ccw(), Some(d + Left));
569 | /// assert_eq!((center + d + (d + Right)).direction_from_center_ccw(), Some(d));
570 | /// }
571 | /// ```
572 | pub fn direction_from_center_ccw(&self) -> Option {
573 |
574 | let x = self.x;
575 | let y = self.y;
576 | let z = self.z();
577 | let zero : I = num::FromPrimitive::from_i8(0).unwrap();
578 |
579 | let xy = if z > zero { x >= y } else { x > y };
580 | let yz = if x > zero { y >= z } else { y > z };
581 | let zx = if y > zero { z >= x } else { z > x };
582 | match (xy, yz, zx) {
583 | (true, true, false) => Some(XZ),
584 | (true, false, false) => Some(XY),
585 | (true, false, true) => Some(ZY),
586 |
587 | (false, false, true) => Some(ZX),
588 | (false, true, true) => Some(YX),
589 | (false, true, false) => Some(YZ),
590 | (false, false, false) => None,
591 | (true, true, true) => panic!("You broke math"),
592 | }
593 | }
594 |
595 | /// Directions from self to `coord`
596 | pub fn directions_to(&self, coord : Coordinate) -> Vec {
597 | (coord - *self).directions_from_center()
598 | }
599 |
600 | /// Direction from self to `coord`
601 | ///
602 | /// In case of diagonals (edge of two major directions), prefers direction that is clockwise
603 | /// from the diagonal.
604 | ///
605 | /// Returns:
606 | /// None if is center
607 | pub fn direction_to_cw(&self, coor : Coordinate) -> Option {
608 | (coor - *self).direction_from_center_cw()
609 | }
610 |
611 | /// Direction from self to `coor`
612 | ///
613 | /// In case of diagonals (edge of two major directions), prefers direction that is
614 | /// counter-clockwise from the diagonal.
615 | ///
616 | /// Returns:
617 | /// None if is center
618 | pub fn direction_to_ccw(&self, coor: Coordinate) -> Option {
619 | (coor - *self).direction_from_center_ccw()
620 | }
621 |
622 | /// Distance between two Coordinates
623 | pub fn distance(&self, c : Coordinate) -> I {
624 | ((self.x - c.x).abs() + (self.y - c.y).abs() + (self.z() - c.z()).abs())
625 | / num::FromPrimitive::from_i8(2).unwrap()
626 | }
627 |
628 | /// An iterator over all coordinates in radius `r`
629 | pub fn range_iter(&self, r : I) -> Range {
630 | Range{
631 | source: *self,
632 | x: -r,
633 | y: max(-r, -(-r)-r),
634 | r,
635 | counter: 0,
636 | }
637 | }
638 |
639 | /// Iterator over each coordinate in a ring
640 | ///
641 | /// Example: Elements in order for Ring of radius 2, Direction ZX, CCW
642 | ///
643 | /// ```norust
644 | /// 8
645 | /// 9 7
646 | /// 10 . 6
647 | /// . .
648 | /// 11 x 5
649 | /// . .
650 | /// 0 . 4
651 | /// 1 3
652 | /// 2
653 | /// ```
654 | ///
655 | /// ```
656 | ///
657 | /// use hex2d::{Coordinate, Spin, XY};
658 | ///
659 | /// let center = Coordinate::new(5, -1);
660 | ///
661 | /// for &c in ¢er.neighbors() {
662 | /// for ring_c in c.ring_iter(5, Spin::CCW(XY)) {
663 | /// assert_eq!(c.distance(ring_c), 5);
664 | /// }
665 | /// }
666 | /// ```
667 | pub fn ring_iter(&self, r : i32, s : Spin) -> Ring {
668 |
669 | let (start_angle, step_angle, start_dir) = match s {
670 | CW(d) => (if r >= 0 { RightBack } else { Left }, Right, d),
671 | CCW(d) => (if r >= 0 { LeftBack } else { Right }, Left, d),
672 | };
673 |
674 | let cur_coord = *self + Coordinate::::from(start_dir).scale(
675 | num::FromPrimitive::from_i32(r).unwrap()
676 | );
677 | let cur_dir = start_dir + start_angle;
678 |
679 |
680 | Ring {
681 | source: *self,
682 | cur_coord,
683 | cur_dir,
684 | step_angle,
685 | r: r.abs(),
686 | ii: 0,
687 | jj: 0,
688 | fuse: false,
689 | }
690 | }
691 | }
692 |
693 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
694 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
695 | /// Iterator over a ring
696 | pub struct Ring {
697 | source: Coordinate,
698 | cur_coord: Coordinate,
699 | cur_dir: Direction,
700 | step_angle: Angle,
701 | r: i32,
702 | ii: i32,
703 | jj: i32,
704 | fuse: bool,
705 | }
706 |
707 | impl<
708 | I: num::Integer
709 | + num::Signed
710 | + std::marker::Copy
711 | + num::NumCast
712 | + num::FromPrimitive
713 | + num::CheckedAdd
714 | + std::marker::Copy
715 | + std::ops::AddAssign,
716 | > Iterator for Ring
717 | {
718 | type Item = Coordinate;
719 |
720 | fn next(&mut self) -> Option {
721 | if self.fuse {
722 | return None;
723 | }
724 | if self.r.is_zero() {
725 | self.fuse = true;
726 | return Some(self.source)
727 | }
728 |
729 | if self.jj >= self.r.abs() {
730 | self.ii += 1;
731 | if self.ii >= 6 {
732 | self.fuse = true;
733 | return None
734 | }
735 | self.cur_dir = self.cur_dir + self.step_angle;
736 | self.jj = 0;
737 | }
738 | self.jj += 1;
739 |
740 | let ret = Some(self.cur_coord);
741 | let cur_dir_coord: Coordinate<_> = self.cur_dir.into();
742 | self.cur_coord = self.cur_coord + cur_dir_coord;
743 |
744 | ret
745 |
746 | }
747 |
748 | fn size_hint(&self) -> (usize, Option) {
749 | if self.fuse {
750 | return (0, Some(0));
751 | }
752 | let total_size: usize = if self.r == 0 { 1 } else { (self.r*6) as usize };
753 | let past: usize = max(0, (self.jj+self.ii*self.r).try_into().unwrap());
754 | (total_size-past , Some(total_size-past))
755 | }
756 | }
757 |
758 | impl<
759 | I: num::Integer
760 | + num::Signed
761 | + std::marker::Copy
762 | + num::NumCast
763 | + num::FromPrimitive
764 | + num::CheckedAdd
765 | + std::marker::Copy
766 | + std::ops::AddAssign,
767 | > iter::FusedIterator for Ring {}
768 |
769 | impl<
770 | I: num::Integer
771 | + num::Signed
772 | + std::marker::Copy
773 | + num::NumCast
774 | + num::FromPrimitive
775 | + num::CheckedAdd
776 | + std::marker::Copy
777 | + std::ops::AddAssign,
778 | > iter::ExactSizeIterator for Ring {}
779 |
780 |
781 |
782 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Ord, PartialOrd)]
783 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
784 | /// Iterator over an range
785 | pub struct Range {
786 | source: Coordinate,
787 | x: I,
788 | y: I,
789 | r: I,
790 | counter: usize,
791 | }
792 |
793 | impl<
794 | I: num::Integer
795 | + num::Signed
796 | + std::marker::Copy
797 | + num::NumCast
798 | + num::FromPrimitive
799 | + num::CheckedAdd
800 | + std::marker::Copy
801 | + std::ops::AddAssign,
802 | > Iterator for Range
803 | {
804 | type Item = Coordinate;
805 |
806 | fn next(&mut self) -> Option {
807 | if self.y > min(self.r, -self.x+self.r) {
808 | if self.x >= self.r {
809 | return None
810 | }
811 | self.x += One::one();
812 | self.y = max(-self.r, -self.x-self.r);
813 | }
814 |
815 | let ret = Some(Coordinate{
816 | x: self.source.x + self.x,
817 | y: self.source.y + self.y,
818 | });
819 | self.y += One::one();
820 | self.counter += 1;
821 | ret
822 | }
823 |
824 | fn size_hint(&self) -> (usize, Option) {
825 | let rc = (if self.r < Zero::zero() { I::one()-self.r } else { self.r }).to_usize().unwrap();
826 | let total_size = 3*(rc+rc*rc)+1;
827 | let current_size = total_size-self.counter;
828 | (current_size, Some(current_size))
829 | }
830 | }
831 |
832 | impl<
833 | I: num::Integer
834 | + num::Signed
835 | + std::marker::Copy
836 | + num::NumCast
837 | + num::FromPrimitive
838 | + num::CheckedAdd
839 | + std::marker::Copy
840 | + std::ops::AddAssign,
841 | > iter::FusedIterator for Range {}
842 |
843 | impl<
844 | I: num::Integer
845 | + num::Signed
846 | + std::marker::Copy
847 | + num::NumCast
848 | + num::FromPrimitive
849 | + num::CheckedAdd
850 | + std::marker::Copy
851 | + std::ops::AddAssign,
852 | >
853 | iter::ExactSizeIterator for Range
854 | {}
855 |
856 |
857 | #[derive(Clone, PartialEq, Debug, PartialOrd)]
858 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
859 | /// Genertic iterator over a line return x, y values
860 | struct LineToGen {
861 | ax: f32,
862 | ay: f32,
863 | bx: f32,
864 | by: f32,
865 | n: I,
866 | i: I,
867 | }
868 |
869 | impl<
870 | I: num::Integer
871 | + num::Signed
872 | + std::marker::Copy
873 | + num::NumCast
874 | + num::FromPrimitive
875 | + num::ToPrimitive
876 | + num::CheckedAdd
877 | + std::marker::Copy
878 | + std::ops::AddAssign,
879 | > Iterator for LineToGen
880 | {
881 | type Item = (f32, f32);
882 |
883 | fn next(&mut self) -> Option {
884 | if self.n == Zero::zero() {
885 | if self.i == Zero::zero() {
886 | self.i += One::one();
887 | return Some((self.ax, self.ay));
888 | } else {
889 | return None;
890 | }
891 | }
892 |
893 | if self.i > self.n {
894 | return None;
895 | }
896 |
897 | let d = self.i.to_f32().unwrap() / self.n.to_f32().unwrap();
898 | let x = self.ax + (self.bx - self.ax) * d;
899 | let y = self.ay + (self.by - self.ay) * d;
900 | self.i += One::one();
901 | Some((x, y))
902 | }
903 |
904 | fn size_hint(&self) -> (usize, Option) {
905 | let origin = Coordinate::::nearest(self.ax, self.ay);
906 | let dest = Coordinate::nearest(self.bx, self.by);
907 | let total_size = origin.distance(dest) + One::one();
908 | let len = total_size-self.i;
909 | let len = len.to_usize().unwrap();
910 | (len, Some(len))
911 | }
912 | }
913 |
914 | impl<
915 | I: num::Integer
916 | + num::Signed
917 | + std::marker::Copy
918 | + num::NumCast
919 | + num::FromPrimitive
920 | + num::CheckedAdd
921 | + std::marker::Copy
922 | + std::ops::AddAssign,
923 | > iter::FusedIterator for LineToGen {}
924 |
925 | impl<
926 | I: num::Integer
927 | + num::Signed
928 | + std::marker::Copy
929 | + num::NumCast
930 | + num::FromPrimitive
931 | + num::ToPrimitive
932 | + num::CheckedAdd
933 | + std::marker::Copy
934 | + std::ops::AddAssign,
935 | >
936 | iter::ExactSizeIterator for LineToGen
937 | {}
938 |
939 | #[derive(Clone, PartialEq, Debug, PartialOrd)]
940 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
941 | /// An iterator over an a line of Coordinates
942 | pub struct LineTo (LineToGen);
943 |
944 | impl<
945 | I: num::Integer
946 | + num::Signed
947 | + std::marker::Copy
948 | + num::NumCast
949 | + num::FromPrimitive
950 | + num::ToPrimitive
951 | + num::CheckedAdd
952 | + std::marker::Copy
953 | + std::ops::AddAssign,
954 | > Iterator for LineTo
955 | {
956 | type Item = Coordinate;
957 | fn next(&mut self) -> Option {
958 | self.0.next().map(|(x, y)| Coordinate::nearest(x, y))
959 | }
960 |
961 | fn size_hint(&self) -> (usize, Option) {
962 | self.0.size_hint()
963 | }
964 | }
965 |
966 | impl<
967 | I: num::Integer
968 | + num::Signed
969 | + std::marker::Copy
970 | + num::NumCast
971 | + num::FromPrimitive
972 | + num::CheckedAdd
973 | + std::marker::Copy
974 | + std::ops::AddAssign,
975 | > iter::FusedIterator for LineTo {}
976 |
977 | impl<
978 | I: num::Integer
979 | + num::Signed
980 | + std::marker::Copy
981 | + num::NumCast
982 | + num::FromPrimitive
983 | + num::ToPrimitive
984 | + num::CheckedAdd
985 | + std::marker::Copy
986 | + std::ops::AddAssign,
987 | >
988 | iter::ExactSizeIterator for LineTo
989 | {}
990 |
991 | #[derive(Clone, PartialEq, Debug, PartialOrd)]
992 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
993 | /// An iterator over an a line of Coordinates, using a lossy algorithm
994 | pub struct LineToLossy (LineToGen);
995 |
996 | impl<
997 | I: num::Integer
998 | + num::Signed
999 | + std::marker::Copy
1000 | + num::NumCast
1001 | + num::FromPrimitive
1002 | + num::ToPrimitive
1003 | + num::CheckedAdd
1004 | + std::marker::Copy
1005 | + std::ops::AddAssign,
1006 | > Iterator for LineToLossy
1007 | {
1008 | type Item = Coordinate;
1009 | fn next(&mut self) -> Option {
1010 | loop {
1011 | let c = self.0.next().map(|(x, y)| Coordinate::nearest_lossy(x, y));
1012 | match c {
1013 | Some(c@Some(_)) => return c,
1014 | Some(None) => continue,
1015 | None => return None,
1016 | }
1017 | }
1018 | }
1019 |
1020 | fn size_hint(&self) -> (usize, Option) {
1021 | (self.0.size_hint().0/2, self.0.size_hint().1)
1022 | }
1023 | }
1024 |
1025 | impl<
1026 | I: num::Integer
1027 | + num::Signed
1028 | + std::marker::Copy
1029 | + num::NumCast
1030 | + num::FromPrimitive
1031 | + num::CheckedAdd
1032 | + std::marker::Copy
1033 | + std::ops::AddAssign,
1034 | > iter::FusedIterator for LineToLossy {}
1035 |
1036 | #[derive(Clone, PartialEq, Debug, PartialOrd)]
1037 | #[cfg_attr(feature="serde-serde", derive(Serialize, Deserialize))]
1038 | /// An iterator over an a line of Coordinates, with edge detection
1039 | pub struct LineToWithEdgeDetection (LineToGen);
1040 |
1041 | impl<
1042 | I: num::Integer
1043 | + num::Signed
1044 | + std::marker::Copy
1045 | + num::NumCast
1046 | + num::FromPrimitive
1047 | + num::ToPrimitive
1048 | + num::CheckedAdd
1049 | + std::marker::Copy
1050 | + std::ops::AddAssign,
1051 | > Iterator for LineToWithEdgeDetection
1052 | {
1053 | type Item = (Coordinate, Coordinate);
1054 | fn next(&mut self) -> Option {
1055 | self.0.next().map(|(x, y)| (
1056 | Coordinate::nearest(x + 0.000001, y + 0.000001),
1057 | Coordinate::nearest(x - 0.000001, y - 0.000001)
1058 | ))
1059 | }
1060 |
1061 | fn size_hint(&self) -> (usize, Option) {
1062 | self.0.size_hint()
1063 | }
1064 | }
1065 |
1066 | impl<
1067 | I: num::Integer
1068 | + num::Signed
1069 | + std::marker::Copy
1070 | + num::NumCast
1071 | + num::FromPrimitive
1072 | + num::CheckedAdd
1073 | + std::marker::Copy
1074 | + std::ops::AddAssign,
1075 | > iter::FusedIterator for LineToWithEdgeDetection {}
1076 |
1077 | impl<
1078 | I: num::Integer
1079 | + num::Signed
1080 | + std::marker::Copy
1081 | + num::NumCast
1082 | + num::FromPrimitive
1083 | + num::ToPrimitive
1084 | + num::CheckedAdd
1085 | + std::marker::Copy
1086 | + std::ops::AddAssign,
1087 | >
1088 | iter::ExactSizeIterator for LineToWithEdgeDetection
1089 | {}
1090 |
1091 | impl From<(I, I)> for Coordinate {
1092 | fn from(xy: (I, I)) -> Self {
1093 | let (x, y) = xy;
1094 | Coordinate::new(x, y)
1095 | }
1096 | }
1097 |
1098 | impl>> Add for Coordinate {
1099 | type Output = Coordinate;
1100 |
1101 | fn add(self, c : T) -> Coordinate {
1102 | let c: Coordinate<_> = c.into();
1103 |
1104 | Coordinate {
1105 | x: self.x + c.x,
1106 | y: self.y + c.y,
1107 | }
1108 | }
1109 | }
1110 |
1111 | impl>> Sub for Coordinate {
1112 | type Output = Coordinate;
1113 |
1114 | fn sub(self, c : T) -> Coordinate {
1115 | let c: Coordinate<_> = c.into();
1116 |
1117 | Coordinate {
1118 | x: self.x - c.x,
1119 | y: self.y - c.y,
1120 | }
1121 | }
1122 | }
1123 |
1124 | impl Neg for Coordinate
1125 | {
1126 | type Output = Coordinate;
1127 |
1128 | fn neg(self) -> Coordinate {
1129 | Coordinate { x: -self.x, y: -self.y }
1130 | }
1131 | }
1132 |
1133 | impl Position
1134 | {
1135 | /// Create a new Position
1136 | pub fn new(coord : Coordinate, dir : Direction) -> Position {
1137 | Position{ coord: coord, dir: dir }
1138 | }
1139 | }
1140 |
1141 | impl From> for Direction
1142 | {
1143 | fn from(pos: Position) -> Self {
1144 | pos.dir
1145 | }
1146 | }
1147 |
1148 | impl From> for Coordinate
1149 | {
1150 | fn from(pos: Position) -> Self {
1151 | pos.coord
1152 | }
1153 | }
1154 |
1155 | impl Add> for Position {
1156 | type Output = Position;
1157 |
1158 | fn add(self, c : Coordinate) -> Position {
1159 | Position {
1160 | coord: self.coord + c,
1161 | dir: self.dir,
1162 | }
1163 | }
1164 | }
1165 |
1166 | impl Sub> for Position
1167 | {
1168 | type Output = Position;
1169 |
1170 | fn sub(self, c : Coordinate) -> Position {
1171 | Position {
1172 | coord: self.coord - c,
1173 | dir: self.dir,
1174 | }
1175 | }
1176 | }
1177 |
1178 | impl Add for Position
1179 | {
1180 | type Output = Position;
1181 |
1182 | fn add(self, a : Angle) -> Position {
1183 | Position {
1184 | coord: self.coord,
1185 | dir: self.dir + a,
1186 | }
1187 | }
1188 | }
1189 |
1190 | impl Direction {
1191 | /// Static array of all directions
1192 | ///
1193 | /// ```
1194 | /// use hex2d::Direction;
1195 | ///
1196 | /// assert_eq!(Direction::all().len(), 6);
1197 | /// ```
1198 | pub fn all() -> &'static [Direction; 6] {
1199 | &ALL_DIRECTIONS
1200 | }
1201 |
1202 | /// Return a vector of an arc including Directions `steps` away from the original Direction both
1203 | /// sides from left to right.
1204 | ///
1205 | /// ```
1206 | /// use hex2d::{Direction};
1207 | /// use hex2d::Angle::*;
1208 | ///
1209 | /// for &d in Direction::all() {
1210 | /// assert_eq!(d.arc(0), vec!(d));
1211 | /// assert_eq!(d.arc(1), vec!(d + Left, d, d + Right));
1212 | /// assert_eq!(d.arc(2), vec!(d + LeftBack, d + Left, d, d + Right, d + RightBack));
1213 | /// assert_eq!(d.arc(3), vec!(d + LeftBack, d + Left, d, d + Right, d + RightBack, d + Back));
1214 | /// }
1215 | /// ```
1216 | pub fn arc(&self, steps : u8) -> Vec {
1217 | match steps {
1218 | 0 => vec!(*self),
1219 | 1 => vec!(*self + Left, *self, *self + Right),
1220 | 2 => vec!(*self + LeftBack, *self + Left, *self, *self + Right, *self + RightBack),
1221 | _ => vec!(*self + LeftBack, *self + Left, *self, *self + Right, *self + RightBack, *self + Back),
1222 | }
1223 | }
1224 |
1225 | /// Create Direction from integer in [0, 6) range
1226 | ///
1227 | /// This should probably be internal
1228 | pub fn from_int(i : I) -> Direction {
1229 | match i.mod_floor(&num::FromPrimitive::from_i8(6).unwrap()).to_u8().unwrap() {
1230 | 0 => YZ,
1231 | 1 => XZ,
1232 | 2 => XY,
1233 | 3 => ZY,
1234 | 4 => ZX,
1235 | 5 => YX,
1236 | _ => panic!()
1237 | }
1238 | }
1239 |
1240 | /// Convert to integer in [0, 6) range
1241 | ///
1242 | /// This should probably be internal
1243 | pub fn to_int(&self) -> I {
1244 | num::FromPrimitive::from_u8(*self as u8).unwrap()
1245 | }
1246 |
1247 | /// Convert to angle for pointy-topped map, in radians, grows clockwise, 0.0 points up
1248 | pub fn to_radians_pointy(&self) -> T {
1249 | T::from(match *self {
1250 | Direction::YZ => PI * (5.5 / 3.0),
1251 | Direction::XZ => PI * (0.5 / 3.0),
1252 | Direction::XY => PI * (1.5 / 3.0),
1253 | Direction::ZY => PI * (2.5 / 3.0),
1254 | Direction::ZX => PI * (3.5 / 3.0),
1255 | Direction::YX => PI * (4.5 / 3.0),
1256 | }).unwrap()
1257 | }
1258 |
1259 | /// Convert to angle for flat-topped map, in radians, grows clockwise, 0.0 points up
1260 | pub fn to_radians_flat(&self) -> T {
1261 | self.to_radians_pointy::() + T::from(PI * (0.5 / 3.0)).unwrap()
1262 | }
1263 | }
1264 |
1265 | impl> Sub for Direction {
1266 | type Output = Angle;
1267 |
1268 | fn sub(self, c : T) -> Angle {
1269 | let c: Direction = c.into();
1270 |
1271 | Angle::from_int::(self.to_int::() - c.to_int::())
1272 | }
1273 | }
1274 |
1275 | impl From for Coordinate {
1276 | fn from(dir: Direction) -> Self {
1277 | let (x, y) = match dir {
1278 | YZ => (0, 1),
1279 | XZ => (1, 0),
1280 | XY => (1, -1),
1281 | ZY => (0, -1),
1282 | ZX => (-1, 0),
1283 | YX => (-1, 1),
1284 | };
1285 |
1286 | Coordinate {
1287 | x: num::FromPrimitive::from_i8(x).unwrap(),
1288 | y: num::FromPrimitive::from_i8(y).unwrap(),
1289 | }
1290 | }
1291 | }
1292 |
1293 | impl Neg for Direction {
1294 | type Output = Direction ;
1295 |
1296 | fn neg(self) -> Direction {
1297 | match self {
1298 | YZ => ZY,
1299 | XZ => ZX,
1300 | XY => YX,
1301 | ZY => YZ,
1302 | ZX => XZ,
1303 | YX => XY,
1304 | }
1305 | }
1306 | }
1307 |
1308 | impl Angle {
1309 | /// Static array of all angles
1310 | ///
1311 | /// ```
1312 | /// use hex2d::Angle;
1313 | ///
1314 | /// assert_eq!(Angle::all().len(), 6);
1315 | /// ```
1316 | pub fn all() -> &'static [Angle; 6] {
1317 | &ALL_ANGLES
1318 | }
1319 |
1320 | /// Create Angle from integer in [0, 6) range
1321 | ///
1322 | /// This should probably be internal
1323 | pub fn from_int(i : I) -> Angle {
1324 | match i.mod_floor(&num::FromPrimitive::from_i8(6).unwrap()).to_u8().unwrap() {
1325 | 0 => Forward,
1326 | 1 => Right,
1327 | 2 => RightBack,
1328 | 3 => Back,
1329 | 4 => LeftBack,
1330 | 5 => Left,
1331 | _ => panic!()
1332 | }
1333 | }
1334 |
1335 | /// Convert to integer in [0, 6) range
1336 | ///
1337 | /// This should probably be internal
1338 | pub fn to_int(&self) -> I {
1339 | num::FromPrimitive::from_u8(*self as u8).unwrap()
1340 | }
1341 | }
1342 |
1343 | impl Add for Direction {
1344 | type Output = Direction;
1345 |
1346 | fn add(self, a : Angle) -> Direction {
1347 | Direction::from_int(self.to_int::() + a.to_int::())
1348 | }
1349 | }
1350 |
--------------------------------------------------------------------------------
/src/test.rs:
--------------------------------------------------------------------------------
1 | // See LICENSE file for more information
2 |
3 | use super::*;
4 | use std::convert::Into;
5 |
6 | fn with_test_points ()>(f : F) {
7 | let offs = [-2i32, -1, 0, 1, 2, 1000, -1000, 1001, -1001];
8 | for &x in offs.iter() {
9 | for &y in offs.iter() {
10 | let p = Coordinate::new(x, y);
11 | f(p)
12 | }
13 | }
14 | }
15 |
16 | #[test]
17 | fn coord_add_and_sub() {
18 | let a = Coordinate::new(-1, 2);
19 | let b = Coordinate::new(3, 4);
20 | let c = Coordinate::new(2, 6);
21 |
22 | assert_eq!(a + b, c);
23 | assert_eq!(c - b, a);
24 | assert_eq!(c - a, b);
25 | }
26 |
27 | #[test]
28 | fn direction_add_and_sub() {
29 | for &d in Direction::all().iter() {
30 | assert_eq!(d + Forward, d);
31 | assert_eq!(d + Right + Left, d);
32 | assert_eq!(d + Right + Right, d + RightBack);
33 | assert_eq!(d + Right + Right + Right, d + Back);
34 | assert_eq!(d + Left + Left, d + LeftBack);
35 | assert_eq!(d + Left + Left + Left, d + Back);
36 | assert_eq!(d + RightBack + RightBack + RightBack, d);
37 | }
38 |
39 | with_test_points(|c : Coordinate| {
40 | for &sd in Direction::all() {
41 | let p = Position::new(c, sd);
42 |
43 | assert_eq!(p + Forward, p);
44 | assert_eq!(p + Right + Left, p);
45 | assert_eq!(p + Right + Right, p + RightBack);
46 | assert_eq!(p + Right + Right + Right, p + Back);
47 | assert_eq!(p + Left + Left, p + LeftBack);
48 | assert_eq!(p + Left + Left + Left, p + Back);
49 | assert_eq!(p + RightBack + RightBack + RightBack, p);
50 | }
51 | });
52 | }
53 |
54 |
55 | #[test]
56 | fn coord_add_and_sub_direction() {
57 | with_test_points(|c : Coordinate| {
58 | assert_eq!(c + XY + YX, c);
59 | assert_eq!(c + ZY + YZ, c);
60 | assert_eq!(c + ZX + XZ, c);
61 | assert_eq!(c + ZX + YZ + XY, c);
62 | assert_eq!(c + XZ + ZY + YX, c);
63 | });
64 | }
65 |
66 | #[test]
67 | fn coord_neighbors() {
68 | with_test_points(|c : Coordinate| {
69 | assert_eq!(c, c.neighbors().iter().fold(c, |sc, n| sc + (c - *n)));
70 | });
71 | }
72 |
73 | #[test]
74 | fn move_circularly() {
75 | with_test_points(|p : Coordinate| {
76 | let mut start = p;
77 | let end = p;
78 |
79 | for &dir in Direction::all().iter() {
80 | start = start + dir;
81 | }
82 |
83 | assert_eq!(start, end);
84 | })
85 | }
86 |
87 | #[test]
88 | fn move_circularly_double() {
89 | with_test_points(|p : Coordinate| {
90 | let mut start = p;
91 | let end = p;
92 |
93 | for &dir in Direction::all().iter() {
94 | start = start + dir + dir;
95 | }
96 |
97 | assert_eq!(start, end);
98 | });
99 | }
100 |
101 |
102 | #[test]
103 | fn coord_range() {
104 | with_test_points(|c : Coordinate| {
105 | assert_eq!(1, c.range_iter(0).count());
106 | assert_eq!(7, c.range_iter(1).count());
107 | assert_eq!(19, c.range_iter(2).count());
108 | assert_eq!(37, c.range_iter(3).count());
109 | assert_eq!((5 + 6 + 7 + 8 ) * 2 + 9, c.range_iter(4).count());
110 | });
111 | }
112 |
113 | #[test]
114 | fn range_distance() {
115 | with_test_points(|c : Coordinate| {
116 | for r in 0..10 {
117 | for p in c.range_iter(r) {
118 | assert!(p.distance(c) <= r);
119 | }
120 | }
121 | });
122 | }
123 |
124 | #[test]
125 | fn simple_rings() {
126 | with_test_points(|c : Coordinate| {
127 | for &d in Direction::all().iter() {
128 | {
129 | // CW r0
130 | let ring = c.ring_iter(0, Spin::CW(d)).collect::>();
131 |
132 | assert_eq!(1, ring.len());
133 | assert_eq!(ring[0], c);
134 | }
135 | {
136 | // CCW r0
137 | let ring = c.ring_iter(0, Spin::CCW(d)).collect::>();
138 |
139 | assert_eq!(1, ring.len());
140 | assert_eq!(ring[0], c);
141 | }
142 | {
143 | // CCW r1
144 | let ring = c.ring_iter(1, Spin::CW(d)).collect::>();
145 |
146 | assert_eq!(6, ring.len());
147 | assert_eq!(ring[0], c + d);
148 | assert_eq!(ring[1], c + (d + Right));
149 | assert_eq!(ring[2], c + (d + RightBack));
150 | assert_eq!(ring[3], c + (d + Back));
151 | assert_eq!(ring[4], c + (d + LeftBack));
152 | assert_eq!(ring[5], c + (d + Left));
153 | }
154 | {
155 | // CCW r1
156 | let ring = c.ring_iter(1, Spin::CCW(d)).collect::>();
157 |
158 | assert_eq!(6, ring.len());
159 | assert_eq!(ring[0], c + d);
160 | assert_eq!(ring[1], c + (d + Left));
161 | assert_eq!(ring[2], c + (d + LeftBack));
162 | assert_eq!(ring[3], c + (d + Back));
163 | assert_eq!(ring[4], c + (d + RightBack));
164 | assert_eq!(ring[5], c + (d + Right));
165 | }
166 | {
167 | // CW r2
168 | let ring = c.ring_iter(2, Spin::CW(d)).collect::>();
169 |
170 | assert_eq!(12, ring.len());
171 | assert_eq!(ring[0], c + d + d);
172 | assert_eq!(ring[1], c + d + d + (d + RightBack));
173 | assert_eq!(ring[7], c - d - d - (d + RightBack));
174 | assert_eq!(ring[11], c + d + d + (d + LeftBack));
175 | }
176 | {
177 | // CCW r2
178 | let ring = c.ring_iter(2, Spin::CCW(d)).collect::>();
179 |
180 | assert_eq!(12, ring.len());
181 | assert_eq!(ring[0], c + d + d);
182 | assert_eq!(ring[1], c + d + d + (d + LeftBack));
183 | assert_eq!(ring[7], c - d - d - (d + LeftBack));
184 | assert_eq!(ring[11], c + d + d + (d + RightBack));
185 | }
186 | {
187 | // CW r-2
188 | let ring = c.ring_iter(-2, Spin::CW(d)).collect::>();
189 |
190 | assert_eq!(12, ring.len());
191 | assert_eq!(ring[0], c - d - d);
192 | assert_eq!(ring[1], c - d - d - (d + RightBack));
193 | assert_eq!(ring[7], c + d + d + (d + RightBack));
194 | assert_eq!(ring[11], c - d - d - (d + LeftBack));
195 | }
196 | }
197 | })
198 | }
199 |
200 | #[test]
201 | fn simple_to_pixel() {
202 |
203 | let p_spacing = PointyTop(2f32);
204 | let f_spacing = FlatTop(2f32);
205 |
206 | {
207 | let c = Coordinate::new(0, 0);
208 | assert_eq!(c.to_pixel(p_spacing), (0f32, 0f32));
209 | assert_eq!(c.to_pixel(f_spacing), (0f32, 0f32));
210 | }
211 |
212 | assert_eq!(Into::>::into((2i32, -1i32)).to_pixel(f_spacing), (6f32, 0f32));
213 | assert_eq!(Into::>::into((-2i32, 1i32)).to_pixel(f_spacing), (-6f32, 0f32));
214 | assert_eq!(Into::>::into((1i32, 1i32)).to_pixel(p_spacing), (0f32, -6f32));
215 | assert_eq!(Into::>::into((2i32, 2i32)).to_pixel(p_spacing), (0f32, -12f32));
216 | }
217 |
218 | #[test]
219 | fn simple_from_pixel() {
220 | for &spacing in [
221 | Spacing::PointyTop(30.0),
222 | Spacing::PointyTop(-40.0),
223 | Spacing::FlatTop(100.0)
224 | ].iter() {
225 | with_test_points(|c : Coordinate| {
226 | let (x, y) = c.to_pixel(spacing);
227 | assert_eq!(c, Coordinate::from_pixel(x, y, spacing));
228 | });
229 | }
230 | }
231 |
232 | #[test]
233 | fn simple_from_pixel_integer() {
234 | for &spacing in [
235 | IntegerSpacing::PointyTop(2, 1),
236 | IntegerSpacing::PointyTop(4, 6),
237 | IntegerSpacing::FlatTop(3, 2),
238 | ].iter() {
239 | with_test_points(|c : Coordinate| {
240 | let ascii_pix = c.to_pixel_integer(spacing);
241 | let (coord, pix_off) = Coordinate::nearest_with_offset(spacing, ascii_pix);
242 | assert_eq!((c, (0, 0)), (coord, pix_off));
243 | });
244 | }
245 | }
246 |
247 | #[test]
248 | fn simple_rotations_around_zero() {
249 | with_test_points(|c : Coordinate| {
250 | assert_eq!(c, c.rotate_around_zero(Left).rotate_around_zero(Right));
251 | assert_eq!(c.rotate_around_zero(LeftBack),
252 | c.rotate_around_zero(Left).rotate_around_zero(Left));
253 | assert_eq!(c.rotate_around_zero(RightBack),
254 | c.rotate_around_zero(Right).rotate_around_zero(Right));
255 | assert_eq!(
256 | c.rotate_around_zero(Back),
257 | c.rotate_around_zero(Right)
258 | .rotate_around_zero(Right)
259 | .rotate_around_zero(Right)
260 | );
261 | assert_eq!(
262 | c.rotate_around_zero(Back),
263 | c.rotate_around_zero(Left)
264 | .rotate_around_zero(Left)
265 | .rotate_around_zero(Left)
266 | );
267 | assert_eq!(
268 | c.rotate_around_zero(Back),
269 | -c
270 | );
271 | });
272 | }
273 |
274 | #[test]
275 | fn simple_rotations_around() {
276 | with_test_points(|c : Coordinate| {
277 | with_test_points(|p : Coordinate| {
278 | assert_eq!(p, p.rotate_around(c, Left).rotate_around(c, Right));
279 | assert_eq!(
280 | p.rotate_around(c, LeftBack),
281 | p.rotate_around(c, Left).rotate_around(c, Left)
282 | );
283 | assert_eq!(
284 | p.rotate_around(c, RightBack),
285 | p.rotate_around(c, Right).rotate_around(c, Right)
286 | );
287 | assert_eq!(
288 | p.rotate_around(c, Back),
289 | p.rotate_around(c, Right)
290 | .rotate_around(c, Right)
291 | .rotate_around(c, Right)
292 | );
293 | assert_eq!(
294 | p.rotate_around(c, Back),
295 | p.rotate_around(c, Left)
296 | .rotate_around(c, Left)
297 | .rotate_around(c, Left)
298 | );
299 | });
300 | });
301 | }
302 |
303 | #[test]
304 | fn simple_direction_from_center() {
305 |
306 | let c = Coordinate::new(0, 0);
307 |
308 | assert_eq!(c.direction_from_center_cw(), None);
309 | assert_eq!(c.direction_from_center_ccw(), None);
310 |
311 | for &dir in Direction::all().iter() {
312 | assert_eq!((c + dir).direction_from_center_cw(), Some(dir));
313 | assert_eq!((c + dir).direction_from_center_ccw(), Some(dir));
314 | assert_eq!((c + dir + (dir + Left)).direction_from_center_cw(), Some(dir));
315 | assert_eq!((c + dir + (dir + Right)).direction_from_center_ccw(), Some(dir));
316 | }
317 | }
318 |
319 | #[test]
320 | fn simple_direction_to() {
321 |
322 | with_test_points(|c : Coordinate| {
323 | assert_eq!(c.direction_to_cw(c), None);
324 | assert_eq!(c.direction_to_ccw(c), None);
325 |
326 | for &dir in Direction::all().iter() {
327 | assert_eq!(c.direction_to_cw(c + dir), Some(dir));
328 | assert_eq!(c.direction_to_ccw(c + dir), Some(dir));
329 | assert_eq!(c.direction_to_cw(c + dir + (dir + Left)), Some(dir));
330 | assert_eq!(c.direction_to_ccw(c + dir + (dir + Right)), Some(dir));
331 | assert_eq!(c.direction_to_cw(c + dir + (dir + Left) + dir + (dir + Left)), Some(dir));
332 | assert_eq!(c.direction_to_ccw(c + dir + (dir + Right) + dir + (dir + Right)), Some(dir));
333 | }
334 | });
335 | }
336 |
337 | #[test]
338 | fn simple_direction_sub() {
339 | for &dir in Direction::all().iter() {
340 | for &angle in Angle::all().iter() {
341 | assert_eq!((dir + angle) - dir, angle);
342 | }
343 | }
344 | }
345 | #[test]
346 | fn simple_line_to() {
347 | with_test_points(|c : Coordinate| {
348 | assert_eq!(c.line_to_iter(c).collect::>(), vec!(c));
349 | });
350 | }
351 |
352 |
--------------------------------------------------------------------------------