* `RasterableVol` represents a volume that is compile-time sized and has
its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen
because such a volume can be used with `VolGrid3d`.
* `RectRasterableVol` represents a volume that is compile-time sized at
least in x and y direction and has its lower bound at `(0, 0, z)`.
On the lower bound in z direction, there's no restriction. An
`RectRasterableVol` can be used with `VolGrid2d`.
Previously, voxels in sparsely populated chunks were stored in a `HashMap`.
However, during usage oftentimes block accesses are followed by subsequent
nearby voxel accesses. Therefore it's possible to provide cache friendliness,
but not with `HashMap`.
This commit replaces the `HashMap` by a data structure where the voxels are
ordered by a morton curve (see https://en.wikipedia.org/wiki/Z-order_curve ).
In sparsely populated chunks (i.e. where most of the voxels are some default
voxel like air) we of course don't want to store the default voxels.
Therefore, we also use an index buffer.
More specifically, the new data structure works as follows.
The voxels of a `Chunk` are conceptually ordered by morton order and then
subdivided into 256 groups along the order. The constant `BLOCK_GROUP_SIZE`
contains the number of voxels per group. The field `vox : Vec<V>` contains
concatenated groups, i.e. its `len()` is invariantly divisable by
`BLOCK_GROUP_SIZE` and every (aligned) sequence of `BLOCK_GROUP_SIZE` voxels
in `self.vox` is a group.
Furthermore there's an index buffer `indices : [u8; 256]` which contains for
each group
* (a) the order in which it has been inserted into `Chunk::vox`,
if the group is contained in `Chunk::vox` or
* (b) 255, otherwise. That case represents that the whole group consists
only of `self.default` voxels.
(Note that 255 is a valid insertion order for case (a) only if `self.vox` is
full and then no other group has the index 255. Therefore there's no
ambiguity.)
Concerning morton order:
* (1) The index buffer `Chunk::indices` unconditionally exists for every
chunk and is sorted by morton order.
* (2) The groups in `Chunk::vox` are not sorted by morton order, but rather
by their insertion order in order to prevent insertions in the middle of
a big vector.
* (3) The voxels inside a group in `Chunk::vox` are sorted by
morton order.
Rationale:
We hope that sorting indices and voxels by morton order provides cache
friendliness for local access patterns. Note that, as mentioned in (2),
`self.vox` is not fully sorted by morton order. Only individual groups
therein are. This is because otherwise most insertions would be expensive.
(As it is, still insertions that trigger a reallocation of `Chunk::vox` are
expensive.) As a future optimization, we could possibly provide an
`self.optimize()` method to sort the groups by morton order (and update the
index buffer accordingly). One could then clone a `Chunk`, run mentioned
method in a background thread and afterwards, if the original `Chunk` wasn't
altered in the meantime, replace it by its optimized version.
The number of groups is 256 such that the index buffer can consist of `u8`s.
This keeps the space requirement for the index buffer low and hence an empty
or almost empty `Chunk` doesn't consume too much memory.