Expand description
N-dimensional arrays, where N is 2 or 3.
The domains of all arrays are located within an ambient space, a signed integer lattice where the elements are Point2i
or
Point3i
. This means they contain data at exactly the set of points in an ExtentN
, and no more.
§Indexing
You can index an array with 3 kinds of coordinates, with Get
traits:
Get*<Stride>
: flat array offsetGet*<Local<N>>
: N-dimensional point in extent-local coordinates (i.e. min =[0, 0, 0]
)Get*<PointN<N>>
: N-dimensional point in global (ambient) coordinates
Indexing assumes that the coordinates are in-bounds of the array, panicking otherwise. Bounds checking is only enabled in debug mode.
§Iteration
Arrays also support fast iteration over extents with ForEach*
trait impls. These methods will only iterate over the
section of the extent which is in-bounds of the array, so it’s impossible to index out of bounds.
use building_blocks_core::prelude::*;
use building_blocks_storage::prelude::*;
let array_extent = Extent3i::from_min_and_shape(Point3i::ZERO, Point3i::fill(64));
let mut array = Array3x1::fill(array_extent, 0);
// Write all points in the extent to the same value.
let write_extent = Extent3i::from_min_and_lub(Point3i::fill(10), Point3i::fill(20));
array.for_each_mut(&write_extent, |_: (), value| *value = 1);
// Only the points in the extent should have been written.
array.for_each(array.extent(), |p: Point3i, value|
if write_extent.contains(p) {
assert_eq!(value, 1);
} else {
assert_eq!(value, 0);
}
);
§Strides
Since Stride
lookups are fast and linear, they are ideal for kernel-based algorithms (like edge/surface detection). Use
the ForEach*<N, Stride>
traits to iterate over an extent and use the linearity of Stride
to access adjacent points.
// Use a more interesting data set, just to show off this constructor.
let mut array = Array3x1::fill_with(extent, |p| if p.x() % 2 == 0 { 1 } else { 0 });
let subextent = Extent3i::from_min_and_shape(Point3i::fill(1), Point3i::fill(62));
// Some of these offsets include negative coordinates, which would underflow when translated
// into an unsigned index. That's OK though, because Stride is intended to be used with modular
// arithmetic.
let offsets = Local::localize_points_array(&Point3i::VON_NEUMANN_OFFSETS);
let mut neighbor_strides = [Stride(0); 6];
array.strides_from_local_points(&offsets, &mut neighbor_strides);
// Sum up the values in the Von Neumann neighborhood of each point, acting as a sort of blur
// filter.
array.for_each(&subextent, |stride: Stride, value| {
let mut neighborhood_sum = value;
for offset in neighbor_strides.iter() {
let adjacent_value = array.get(stride + *offset);
neighborhood_sum += adjacent_value;
}
});
This means you keep the performance of simple array indexing, as opposed to indexing with a Point3i
, which requires 2
multiplications to convert to a Stride
. You’d be surprised how important this difference can be in tight loops.
§Storage
By default, Array
uses a Vec
to store elements. But any type that implements Deref<Target = [T]>
or DerefMut<Target = [T]>
should be usable. This means you can construct an array with most pointer types.
// Borrow `array`'s values for the lifetime of `other_array`.
let array = Array3x1::fill(extent, 1);
let other_array = Array3x1::new_one_channel(extent, array.channels().store().as_slice());
assert_eq!(other_array.get(Stride(0)), 1);
// A stack-allocated array.
let mut data = [1; 32 * 32 * 32];
let mut stack_array = Array3x1::new_one_channel(extent, &mut data[..]);
*stack_array.get_mut(Stride(0)) = 2;
assert_eq!(data[0], 2);
// A boxed array.
let data: Box<[u32]> = Box::new([1; 32 * 32 * 32]); // must forget the size
let box_array = Array3x1::new_one_channel(extent, data);
box_array.for_each(&extent, |p: Point3i, value| assert_eq!(value, 1));
§Multichannel
It’s often the case that you have multiple data types to store per spatial dimension. For example, you might store geometry
data like Sd8
as well as a voxel type identifier. While you can put these in a struct, that may not be the most efficient
option. If you only need access to one of those fields of the struct for a particular algorithm, then you will needlessly
load the entire struct into cache. To avoid this problem, Array
supports storing multiple data “channels” in
structure-of-arrays (SoA) style.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct VoxelId(u8);
// This means 3D with 2 channels. Although we pass in a tuple, the two data
// types are internally stored in separate arrays.
let mut array = Array3x2::fill(extent, (VoxelId(0), 1.0));
// This array supports all of the usual access traits and maps the channels
// to tuples as you would expect.
let p = Point3i::fill(1);
assert_eq!(array.get(p), (VoxelId(0), 1.0));
assert_eq!(array.get_ref(p), (&VoxelId(0), &1.0));
assert_eq!(array.get_mut(p), (&mut VoxelId(0), &mut 1.0));
// Here we choose to access just one channel, and there is no performance penalty.
array.borrow_channels_mut(|(_id, dist)| dist).for_each_mut(&extent, |p: Point3i, dist| {
let r = p.dot(p);
*dist = (r as f32).sqrt();
});
// And if we want to copy just one of those channels into another map, we can
// create a new array that borrows just one of the channels.
let mut dst = Array3x1::fill(extent, 0.0);
let src = array.borrow_channels(|(_id, dist)| dist);
copy_extent(&extent, &src, &mut dst);
Re-exports§
pub use channels::*;
pub use compression::*;
pub use multichannel_aliases::*;
Modules§
Structs§
- Array
- A map from lattice location
PointN<N>
to dataT
, stored as a flat array. - Array
ForEach - All information required to do strided iteration over an extent of a single array.
- Chunk
Units - A newtype wrapper for
PointN
where each point represents exactly one chunk. - Local
- Map-local coordinates.
- Lock
Step Array ForEach - All information required to do strided iteration over two arrays in lock step.
- Stride
- The most efficient coordinates for slice-backed lattice maps. A single number that translates directly to a slice offset.
Traits§
- Array
Indexer - Indexed
Array - When a lattice map implements
IndexedArray
, that means there is some underlying array with the location and shape dictated by the extent.
Type Aliases§
- Array2
ForEach - A 2D
ArrayForEach
. - Array3
ForEach - A 3D
ArrayForEach
. - Array
Nx1 - Array
Nx2 - Array
Nx3 - Array
Nx4 - Array
Nx5 - Array
Nx6 - Chunk
Units2 - Chunk
Units3 - Local2i
- Map-local coordinates, wrapping a
Point2i
. - Local3i
- Map-local coordinates, wrapping a
Point3i
. - Lock
Step Array ForEach2 - Lock
Step Array ForEach3