Skip to content

Commit 30235d2

Browse files
committed
move(/wiki/datastructures;/wiki/introduction/storage): basic explanation doesn't belong there
1 parent 7b7cd63 commit 30235d2

File tree

2 files changed

+35
-69
lines changed

2 files changed

+35
-69
lines changed

content/wiki/datastructures/index.md

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,14 @@ categories = ["datastructures"]
77
tags = ["datastructures", "storage", "spatial-acceleration"]
88
+++
99

10-
The simplest way to store voxels is to define a three-dimensional array of elements (be it `struct`s or `integer`s), where each element represents a single voxel:
11-
12-
```c#
13-
int width = ...;
14-
int height = ...;
15-
int depth = ...;
16-
var voxels = new VOXEL[width][height][depth];
17-
18-
// Set a voxel:
19-
voxels[x][y][z] = voxel;
20-
21-
// Get a voxel:
22-
var voxel = voxels[x][y][z];
23-
```
24-
25-
However, because storing *arrays within arrays* often means having *pointers pointing at pointers* (which ain't good for [various reasons](/wiki/optimization) we won't get into here), it is generally recommended to use a one-dimensional array with a clearly defined *spatial indexing scheme*.
26-
27-
Here's an example:
28-
29-
```c#
30-
--snip--
31-
// Spatial Indexing Scheme is: Y-height, Z-depth, X-width
32-
// The arrays size is just all axes multiplied together:
33-
var voxels = new VOXEL[height * depth * width];
34-
35-
// Our Indexing Formula is the indexing scheme in reverse,
36-
// with the components being multiplied subsequently:
37-
// x + z*width + y*width*depth
38-
39-
// So let's define a function (here a lambda) for it:
40-
var idx = (int x, int y, int z) => (x + z*width + y*width*depth);
41-
// ^ NOTE: You may want to throw an error right here
42-
// if either the coordinate components
43-
// or the resulting index are out of bounds.
44-
45-
// Set a voxel:
46-
voxels[idx(x,y,z)] = voxel;
47-
48-
// Get a voxel:
49-
var voxel = voxels[idx(x,y,z)];
50-
```
51-
52-
Now, storing voxels in a plain array like this is perfectly fine for small scenes...
53-
54-
However, for larger scenes, we'll have to use a data-structure that allows both loading and purging *parts of our volume* (called [Chunks](/wiki/chunking) or Bricks) from memory, nearly in realtime, without slowing down the *access times* of our voxel data too much.
10+
Storing voxels in a plain array is perfectly fine for small scenes,
11+
but as we increase the size (and thus volume) of our scene,
12+
we will inevitably run into various limits, like the *size of our RAM*...
13+
14+
As such, we'll have to use a data-structure that allows both
15+
loading and purging *parts of our volume* (called [Chunks](/wiki/chunking) or Bricks)
16+
from memory, nearly in realtime, without slowing down the *access times* of our voxel data,
17+
or the performance of world generation, *too* much.
5518

5619
> **Note:** These techniques can often be combined.
5720

content/wiki/introduction/storage.md

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ pub struct VoxelGrid {
5050
values: [[[Voxel; GRID_SIZE]; GRID_SIZE]; GRID_SIZE];
5151
// Well ain't that nice to look at, eh?
5252
}
53+
54+
// Define methods to GET and SET voxels:
55+
impl VoxelGrid {
56+
pub fn get(&self, x: u32, y: u32, z: u32) -> Voxel {
57+
self.values[x][y][z]
58+
}
59+
60+
pub fn set(&self, x: u32, y: u32, z: u32, v: Voxel) {
61+
*self.values[x][y][z] = v;
62+
}
63+
}
64+
5365
```
5466

5567
Now accessing it is pretty simple:
@@ -64,35 +76,26 @@ let mut volume = VoxelGrid {
6476
let (x,y,z) = (0, 1, 2);
6577

6678
// Get a voxel:
67-
let v = volume.values[x][y][z];
79+
let v = volume.get(x, y, z);
6880

6981
// Set a voxel:
70-
*volume.values[x][y][z] = v;
71-
```
72-
73-
But what happens if `x`, `y` or `z` go *outside* the volume? We might get an error and crash!
74-
75-
Let's prevent that by defining *accessor functions* and then ***only*** use these:
76-
77-
```rust
78-
impl VoxelGrid {
79-
pub fn get(&self, x: u32, y: u32, z: u32) -> Option<Voxel> {
80-
self.values.get(x)?.get(y)?.get(z)
81-
}
82-
83-
pub fn set(&self, x: u32, y: u32, z: u32, v: Voxel) -> Option<()> {
84-
*self.values.get_mut(x)?.get_mut(y)?.get_mut(z) = v;
85-
}
86-
}
82+
volume.set(x, y, z, v);
8783
```
8884

89-
Alas, this shows us one of three annoyances with using 3D arrays:
90-
91-
- Accessing elements always requires us to 'jump' trough two levels of indirection.
92-
- Iterating/looping over our voxels requires *three* nested loops, which is a pain to write.
93-
- Creating and filling a 3D array is, unsurprisingly, quite messy.
85+
{% info_notice() %}
86+
**Note on 3D arrays:**
87+
In the vast majority of languages, 3D arrays are implemented as *"arrays of pointers to arrays of pointers to arrays of values"* (sometimes referred to as "jagged arrays"), which has a few downsides:
88+
89+
- If the memory allocator doesn't play nice,
90+
our innermost arrays may be spread all over the place (i.e.: fragmentation).
91+
- Accessing any value requires dereferencing three-ish pointers,
92+
which can and will trash the cache and thus access speed.
93+
- Iterating/looping over them *always* requires three nested loops.
94+
- Creating and (de)serializing 3d arrays tends to be a messy affair.
95+
{% end %}
9496

95-
As such, we will now go ahead and make our array *flat*, turning it one-dimensional!
97+
For a variety of reasons (see note above) and reasons yet to be revealed,
98+
we will now go ahead and *flatten* our array, turning it one-dimensional!
9699

97100
```rust
98101
pub struct VoxelGrid {

0 commit comments

Comments
 (0)