Skip to content

Voxel and Voxel Data Encoding

This page describes how voxel encoding and voxel data encoding works in Foxel and provides additional sample code to demonstrate it.

Voxel Encoding

Voxels in Foxel are encoded as 32-bit unsigned integers. The following image shows how the four bytes of the integer are used:

Voxel Encoding

BitsPropertyValid valuesDescription
🟨Selection bit[0..1]If thie selection bit is 1 then the voxel is selected.
🟥MaterialID[0..127]The MaterialID is the index of the material in the material map.
🟫This byte is currently unused and reserved for an undisclosed future feature.
🟩ColorID[0..255]The ColorID is the index of the color in the Color Palette.
🟦ShapeID[0..5]The ShapeID specifies the shape of the voxel.
🟪RotationID[1..24]The RotationID specifies the rotation of the voxel as one of the 24 box rotations.

NOTE

Note that RotationID starts with 1 and cannot be 0. This ensures that solid voxels are always non-zero.

Also note that ShapeID cannot be 7 because it is used for run-length encoding.

Voxel Data Encoding

Foxel stores the voxels of a 3-dimensional grid, or voxel grid, in one-dimensional unsigned integer arrays. Since a voxel itself does not know its position in the voxel grid, each voxel must be stored in a defined order. To complicate things, it is also important to use data compression, since 3-dimensional voxel grids take up a lot of memory when uncompressed. To solve the position and memory issues, the voxel data in Foxel is stored as run-length encoded (RLE) columns.

To enable RLE an encoded flag is needed to indicate that the next voxel is repeated x times:

C
unit rleflag = (runlength << 8u) | 250u;

Sample Code

The following code samples are intended to demonstrate the encoding/decoding of voxels and voxel data.

Decoding Voxels

The following sample code demonstrates how to decode voxels:

c
uint voxel;
vec4 materials[128];
vec3 colors[256];

// ...
// add code to initialize voxel, materials and colors
// ...

// solid/air test
bool isSolid = (voxel != 0u);
bool isAir = (voxel == 0u);

// decode
uint rotationID = voxel & 31u;
uint shapeID = (voxel >> 5u) & 7u;
uint colorID = (voxel >> 8u) & 255u;
uint materialID = (voxel >> 24u) & 127u;
bool selected = ((voxel & 2147483648u) != 0u);

// validate
bool isValid = (voxel != 0u && rotationID > 0u && rotationID < 25u && shapeID < 7u); 

// get color
vec3 color = colors[colorID];

// get material
vec4 material = materials[materialID];
float metallic = material.x;
float roughness = material.y;
float emissive = material.z;
float alpha = material.w;

// Run-length encoding RLE
bool isRLE = (voxel & 250u == 250u);
uint runlength = (voxel >> 8u) & 16777215u;

Encoding Voxel Data

The following sample code demonstrates how to encode voxel data. It takes the 3-dimensional grid array as input and encodes it into the 1-dimensional data array.

C
uint grid[][][];

// ...
// add code to initialize grid
// ...

uint data[];
uint voxel;
uint rleflag;
int sizex = sizeof(grid);
int sizey = sizeof(grid[0]);
int sizez = sizeof(grid[0][0]);
int index = 0;

for (int x = 0; x < sizex; x++)
{
	for (int z = 0; z < sizez; z++)
	{
		runlength = 1;
		
		for (int y = 0; y < sizey; y++)
		{
			voxel = grid[x, y, z];
			
			if (y < sizey - 1 && voxel = grid[x, y + 1, z])
			{
				runlength++;
			}
			else
			{
				if (runlength == 1) 
				{
					data[index] = voxel;
					index++;
				}
				else
				{
					rleflag = (runlength << 8u) | 250u;
					data[index] = rleflag;
					data[index + 1] = voxel;
					index += 2;
					runlength = 1;
				}
			}
		}
	}
}

NOTE

The size of the data array is unknown before encoding. One better solution would be to use a stream instead of an array.


Decoding Voxel Data

The following sample code demonstrates how to decode voxel data. The size of the resulting voxel grid must be passed in addition to the data. The code takes the 1-dimensional data array as input and decodes it into the 3-dimensional grid array.

C
uint sizex;
uint sizey;
uint sizez;
unit data[];

// ...
// add code to initialize sizex, sizey, sizez, and data
// ...

uint datasize = sizeof(data);
uint grid[][][];
int index = 0;
int runlength;
uint value;
int x = 0;
int y = 0;
int z = 0;

while (index < datasize)
{
	value = data[index];
	
	if (value & 250u == 250u)
	{
		runlength = (value >> 8u) & 16777215u;
		value = data[index + 1];
		
		for (int i = 0; i < runlength; i++)
		{
			grid[x][y + i][z] = value;
		}
		
		index += 2;
		y += runlength;
	}
	else
	{
		grid[x][y][z];
		index++;
		y++;
	}
	
	if (y = sizey)
	{
		y = 0;
		z++;
		
		if (z = sizez) 
		{
			z = 0;
			x++;
		}
	}
}

WARNING

The above code does not handle exceptions. If the data is corrupted or the grid size passed is incorrect, an access violation is very likely.