Skip to content

Commit

Permalink
Split neighbour_location_on_axis into a wrapping and non-wrapping ver…
Browse files Browse the repository at this point in the history
…sion (#23)

* Split neighbour_location_on_axis into a wrapping and non-wrapping version

* Bump version number

Co-authored-by: alexmadeathing <awildaudionerd@gmail.com>
  • Loading branch information
alexmadeathing and alexmadeathing authored Apr 4, 2022
1 parent 9d0937d commit 6e2d719
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 27 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "insides"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
license-file = "LICENSE.md"
description = "A compact, high performance space filling curve library for Rust."
Expand All @@ -14,7 +14,7 @@ keywords = ["morton", "hilbert", "curve", "quadtree", "octree"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dilate = "0.6.1"
dilate = "0.6.2"

[dev-dependencies]
lazy_static = "1.4.0" # For test data
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ First, link insides into your project's cargo.toml.
Check for the latest version at [crates.io](https://crates.io/crates/insides):
```toml
[dependencies]
insides = "0.1.1"
insides = "0.1.2"
```

Next, import insides into your project and try out some of the features:
Expand Down
63 changes: 50 additions & 13 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,14 @@ pub(crate) mod tests {
let sfc = SFC::from_index(NumTraits::from_usize(i));
let coords = sfc.coords();
for axis in 0..D {
let mut sib_coords = coords;
sib_coords[axis] =
let mut expected_coords = coords;
expected_coords[axis] =
if coords[axis].bit_and(NumTraits::one()) == NumTraits::zero() {
coords[axis].add(NumTraits::one())
} else {
coords[axis].sub(NumTraits::one())
};
assert_eq!(sfc.sibling_on_axis(axis).coords(), sib_coords);
assert_eq!(sfc.sibling_on_axis(axis).coords(), expected_coords);
}
}
}
Expand All @@ -265,22 +265,22 @@ pub(crate) mod tests {
let sfc = SFC::from_index(NumTraits::from_usize(i));
let coords = sfc.coords();
for axis in 0..D {
let mut sib_coords = coords;
sib_coords[axis] =
let mut expected_coords = coords;
expected_coords[axis] =
if coords[axis].bit_and(NumTraits::one()) == NumTraits::zero() {
coords[axis].add(NumTraits::one())
} else {
coords[axis]
};
assert_eq!(sfc.sibling_or_same_on_axis(axis, QueryDirection::Positive).coords(), sib_coords);
assert_eq!(sfc.sibling_or_same_on_axis(axis, QueryDirection::Positive).coords(), expected_coords);

sib_coords[axis] =
expected_coords[axis] =
if coords[axis].bit_and(NumTraits::one()) == NumTraits::one() {
coords[axis].sub(NumTraits::one())
} else {
coords[axis]
};
assert_eq!(sfc.sibling_or_same_on_axis(axis, QueryDirection::Negative).coords(), sib_coords);
assert_eq!(sfc.sibling_or_same_on_axis(axis, QueryDirection::Negative).coords(), expected_coords);
}
}
}
Expand All @@ -296,22 +296,54 @@ pub(crate) mod tests {
let sfc = SFC::from_index(NumTraits::from_usize(i));
let coords = sfc.coords();
for axis in 0..D {
let mut nbr_coords = coords;
nbr_coords[axis] =
let expected = if coords[axis] != SFC::COORD_MAX {
let mut nbr_coords = coords;
nbr_coords[axis] = coords[axis].add(NumTraits::one());
Some(SFC::from_coords(nbr_coords))
} else {
None
};
assert_eq!(sfc.neighbour_on_axis(axis, QueryDirection::Positive), expected);

let expected = if coords[axis] != NumTraits::zero() {
let mut nbr_coords = coords;
nbr_coords[axis] = coords[axis].sub(NumTraits::one());
Some(SFC::from_coords(nbr_coords))
} else {
None
};
assert_eq!(sfc.neighbour_on_axis(axis, QueryDirection::Negative), expected);
}
}
}

pub fn neighbour_on_axis_wrapping_is_correct()
where
SFC: Neighbours<D>,
<SFC as SpaceFillingCurve<D>>::Coord: Debug,
{
let num_indices: usize = SFC::INDEX_MAX.to_usize().min(MAX_TESTED_INDICES);
for i in 0..num_indices {
// It's a shame that we have to rely on other SFC methods to test this trait... not sure of a better solution yet
let sfc = SFC::from_index(NumTraits::from_usize(i));
let coords = sfc.coords();
for axis in 0..D {
let mut expected_coords = coords;
expected_coords[axis] =
if coords[axis] != SFC::COORD_MAX {
coords[axis].add(NumTraits::one())
} else {
NumTraits::zero()
};
assert_eq!(sfc.neighbour_on_axis(axis, QueryDirection::Positive).coords(), nbr_coords);
assert_eq!(sfc.neighbour_on_axis_wrapping(axis, QueryDirection::Positive).coords(), expected_coords);

nbr_coords[axis] =
expected_coords[axis] =
if coords[axis] != NumTraits::zero() {
coords[axis].sub(NumTraits::one())
} else {
SFC::COORD_MAX
};
assert_eq!(sfc.neighbour_on_axis(axis, QueryDirection::Negative).coords(), nbr_coords);
assert_eq!(sfc.neighbour_on_axis_wrapping(axis, QueryDirection::Negative).coords(), expected_coords);
}
}
}
Expand Down Expand Up @@ -389,6 +421,11 @@ pub(crate) mod tests {
fn neighbour_on_axis_is_correct() {
SpaceFillingCurveTester::<TestedCurve, $d>::neighbour_on_axis_is_correct();
}

#[test]
fn neighbour_on_axis_wrapping_is_correct() {
SpaceFillingCurveTester::<TestedCurve, $d>::neighbour_on_axis_wrapping_is_correct();
}
}
}
)+}
Expand Down
63 changes: 55 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
//! assert_eq!(location.coords(), [1, 2, 3]);
//! ```

use core::{fmt::Debug, hash::Hash};

#[doc(hidden)]
pub use dilate::*;

Expand Down Expand Up @@ -114,7 +116,7 @@ pub enum QueryDirection {
/// Provides conversion to and from coordinates
// I'd really love to get rid of the generic parameter here but I think it's waiting on:
// https://github.com/rust-lang/rust/issues/76560
pub trait SpaceFillingCurve<const D: usize> {
pub trait SpaceFillingCurve<const D: usize>: Sized + Ord + Copy + Default + Debug + Hash {
/// Coordinate type
type Coord: CurveCoord;

Expand Down Expand Up @@ -262,6 +264,10 @@ pub trait Neighbours<const D: usize>: SpaceFillingCurve<D> {
/// It is equvalent to adding 1 to or subtracting 1 from the encoded
/// coordinate.
///
/// If the neighbour would cause a coordinate to wrap, this method returns
/// None instead. For a wrapping version, please see
/// [neighbour_on_axis_wrapping()](Neighbours::neighbour_on_axis_wrapping()).
///
/// Unlike the [Siblings] implementation, methods in the Neighbours trait
/// may cross cluster boundaries.
///
Expand All @@ -274,13 +280,54 @@ pub trait Neighbours<const D: usize>: SpaceFillingCurve<D> {
///
/// let location = Morton::<Expand<u16, 3>, 3>::from_coords([1, 2, 3]);
///
/// assert_eq!(location.neighbour_on_axis(0, QueryDirection::Negative).coords(), [0, 2, 3]);
/// assert_eq!(location.neighbour_on_axis(1, QueryDirection::Negative).coords(), [1, 1, 3]);
/// assert_eq!(location.neighbour_on_axis(2, QueryDirection::Negative).coords(), [1, 2, 2]);
/// assert_eq!(location.neighbour_on_axis(0, QueryDirection::Negative).unwrap().coords(), [0, 2, 3]);
/// assert_eq!(location.neighbour_on_axis(1, QueryDirection::Negative).unwrap().coords(), [1, 1, 3]);
/// assert_eq!(location.neighbour_on_axis(2, QueryDirection::Negative).unwrap().coords(), [1, 2, 2]);
///
/// assert_eq!(location.neighbour_on_axis(0, QueryDirection::Positive).unwrap().coords(), [2, 2, 3]);
/// assert_eq!(location.neighbour_on_axis(1, QueryDirection::Positive).unwrap().coords(), [1, 3, 3]);
/// assert_eq!(location.neighbour_on_axis(2, QueryDirection::Positive).unwrap().coords(), [1, 2, 4]);
///
/// let edge_location = Morton::<Expand<u16, 2>, 2>::from_coords([0, u16::MAX]);
/// assert_eq!(edge_location.neighbour_on_axis(0, QueryDirection::Negative), None);
/// assert_eq!(edge_location.neighbour_on_axis(1, QueryDirection::Positive), None);
/// ```
fn neighbour_on_axis(&self, axis: usize, direction: QueryDirection) -> Option<Self>;

/// Get neighbour location on axis with wrapping
///
/// This method gets the neighbour location in a direction along on an axis.
/// It is equvalent to adding 1 to or subtracting 1 from the encoded
/// coordinate.
///
/// This method allows coordinates to wrap between zero and
/// [COORD_MAX](SpaceFillingCurve::COORD_MAX). For a non-wrapping version,
/// please see
/// [neighbour_on_axis()](Neighbours::neighbour_on_axis()).
///
/// Unlike the [Siblings] implementation, methods in the Neighbours trait
/// may cross cluster boundaries.
///
/// # Panics
/// Panics if parameter 'axis' is greater than or equal to D
///
/// # Examples
/// ```rust
/// use insides::*;
///
/// let location = Morton::<Expand<u16, 3>, 3>::from_coords([1, 2, 3]);
///
/// assert_eq!(location.neighbour_on_axis_wrapping(0, QueryDirection::Negative).coords(), [0, 2, 3]);
/// assert_eq!(location.neighbour_on_axis_wrapping(1, QueryDirection::Negative).coords(), [1, 1, 3]);
/// assert_eq!(location.neighbour_on_axis_wrapping(2, QueryDirection::Negative).coords(), [1, 2, 2]);
///
/// assert_eq!(location.neighbour_on_axis_wrapping(0, QueryDirection::Positive).coords(), [2, 2, 3]);
/// assert_eq!(location.neighbour_on_axis_wrapping(1, QueryDirection::Positive).coords(), [1, 3, 3]);
/// assert_eq!(location.neighbour_on_axis_wrapping(2, QueryDirection::Positive).coords(), [1, 2, 4]);
///
/// assert_eq!(location.neighbour_on_axis(0, QueryDirection::Positive).coords(), [2, 2, 3]);
/// assert_eq!(location.neighbour_on_axis(1, QueryDirection::Positive).coords(), [1, 3, 3]);
/// assert_eq!(location.neighbour_on_axis(2, QueryDirection::Positive).coords(), [1, 2, 4]);
/// let edge_location = Morton::<Expand<u16, 2>, 2>::from_coords([0, u16::MAX]);
/// assert_eq!(edge_location.neighbour_on_axis_wrapping(0, QueryDirection::Negative).coords(), [u16::MAX, u16::MAX]);
/// assert_eq!(edge_location.neighbour_on_axis_wrapping(1, QueryDirection::Positive).coords(), [0, 0]);
/// ```
fn neighbour_on_axis(&self, axis: usize, direction: QueryDirection) -> Self;
fn neighbour_on_axis_wrapping(&self, axis: usize, direction: QueryDirection) -> Self;
}
19 changes: 16 additions & 3 deletions src/morton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,24 @@ where
DM::Dilated: CurveIndex,
{
#[inline]
fn neighbour_on_axis(&self, axis: usize, direction: QueryDirection) -> Self {
fn neighbour_on_axis(&self, axis: usize, direction: QueryDirection) -> Option<Self> {
debug_assert!(axis < D, "Parameter 'axis' exceeds maximum");

let coord = DilatedInt::<DM>::new(self.0.shr(axis).bit_and(DM::DILATED_MAX));
let coord = match direction {
QueryDirection::Positive => if coord.value() < DM::DILATED_MAX { Some(coord.add_one()) } else { None },
QueryDirection::Negative => if coord.value() > NumTraits::zero() { Some(coord.sub_one()) } else { None },
};
coord.map(|coord| {
let index = self.0.bit_and(DM::DILATED_MAX.shl(axis).bit_not());
Self(index.bit_or(coord.value().shl(axis)))
})
}

#[inline]
fn neighbour_on_axis_wrapping(&self, axis: usize, direction: QueryDirection) -> Self {
debug_assert!(axis < D, "Parameter 'axis' exceeds maximum");

// This is a faster eqivalent of converting to coords, adding or subtracting one, then converting back
// It's faster because it bypasses the undilation and dilation stage and instead uses dilated arithmetic
let coord = DilatedInt::<DM>::new(self.0.shr(axis).bit_and(DM::DILATED_MAX));
let coord = match direction {
QueryDirection::Positive => coord.add_one(),
Expand Down

0 comments on commit 6e2d719

Please sign in to comment.