Module building_blocks_storage::array[][src]

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 offset
  • Get*<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::*;

Modules

Structs

A map from lattice location PointN<N> to data T, stored as a flat array.

All information required to do strided iteration over an extent of a single array.

A newtype wrapper for PointN where each point represents exactly one chunk.

Map-local coordinates.

All information required to do strided iteration over two arrays in lock step.

The most efficient coordinates for slice-backed lattice maps. A single number that translates directly to a slice offset.

Traits

When a lattice map implements IndexedArray, that means there is some underlying array with the location and shape dictated by the extent.

Type Definitions

A 2D ArrayForEach.

A 3D ArrayForEach.

Map-local coordinates, wrapping a Point2i.

Map-local coordinates, wrapping a Point3i.