├── .gitignore ├── .travis.yml ├── .vimrc ├── Cargo.toml ├── LICENSE ├── Makefile ├── Makefile.defs ├── README.md └── src ├── lib.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | cache: cargo 7 | matrix: 8 | allow_failures: 9 | - rust: nightly 10 | env: 11 | - DOCS_DEFAULT_MODULE=hex2d 12 | script: 13 | - make test doc 14 | 15 | after_success: | 16 | cargo doc \ 17 | && echo '' > target/doc/index.html && \ 18 | sudo pip install ghp-import && \ 19 | ghp-import -n target/doc && \ 20 | git push -qf "https://${TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git" gh-pages 21 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | autocmd FileType rust set tabstop=4 expandtab shiftwidth=4 softtabstop=4 2 | autocmd FileType rust set foldmethod=syntax foldnestmax=1 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hex2d" 3 | version = "1.1.0" 4 | authors = [ "Dawid Ciężarkiewicz " ] 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 | Build Status 6 | 7 | 8 | crates.io 9 | 10 | Gitter Chat 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 | --------------------------------------------------------------------------------