Making a voxel engine
Tags: voxel, typescript, 3d, javascript
Published: 2020-11-11 05:53:13

---
series: voxel
---
## Introduction 

This series of posts will focus on creating a voxel engine, from scratch, based on BabylonJS for the low-level 3D routines support.

To begin, here is in the video below, the first target we will have to reach, in order to manage the rendering of the world.

{% youtube _RZ6hRMCGF8%}

## So what is a voxel ?
To keep it simple, a voxel is in 3D what a pixel is in 2D. It is value in grid, in a 3D space.

Strictly speaking, the voxel is like a pixel, in the meaning that it has only one value, its color. 

Voxel engines generally have a little more flexibility in the degree to which the display of a voxel is done. It can display a cube single colored, or textured as in Minecraft.

## So displaying cubes is not a big deal, isn't it ?
Short answer : Yes... and no.

A 3D engine, in order to keep a good frame rate, can apply many optimizations to the 3D scene to render.

It can hide the non visible objects, or simplify the objects according to the camera distance.

The problem with voxels is that you will have a very large quantity of cube, so even if you try to hide some of them, you will quickly struggle in rendering speed.

Moreover, a cube is a simple geometric shape, and therefore, simplifying this object cannot be done without severely deforming it. Remove one node and the cube becomes anything you want except... a simpler cube.

## So okay, but where to start then?
Let's start with something basic, which is to define some target functionalities that we are going to implement.

We're going to take our inspiration from the way Minecraft handles the rendering of worlds in the game, at least in the early versions of the game.

We will try to use as few technical terms as possible, just the bare minimum required, in order to keep all the explanations understandable to everyone.

## World structure

### The world
A world represents a set of voxels that it will be possible to display.The world is divided into regions.

### The region
A region represents a piece of the world. Each region has the same number of voxels. A region is also represented by a 3D coordinate. A region is composed by a data chunk.

### A chunk
A chunk is composed of a set of voxels, in a 3-dimensional grid, where each dimension is the same size. This can be simplified as a cube filled with small cubes.

Let's assume for example that a data chunk is composed of 3 dimensions of size 32. A region thus has 32*32*32 voxels, a total of 32768 voxels.

If our world has 100*100 regions per layer and let's say 3 layers of height, we will have a total of 100*100*3 regions, so 30000 regions.

---

Our world will thus have a total of 100*100*3*32768 = 983 040 000 voxels. Our very small world already has close to a billion potential voxels.

## Block definition
Our voxel, in our engine, will be presented as a block, more complex in structure than a simple 3D point.

```ts
export type Block = {
  name  : string; // Block name
  guid  : string; // Unique global Id
  uid   : number; // Unique local id
  sidesTex : [ // Array of textures
    string, // BACK
    string, // FRONT
    string, // RIGHT
    string, // LEFT
    string, // TOP
    string  // BOTTOM
  ];
  size: [ // Edges size
    number, // WIDTH
    number, // HEIGHT
    number  // DEPTH
  ];
  type    : string; // GAZ, LIQUID, BLOCK
  opacity : number;
  speed   : number; // 0 - 1
};
```
So we have the smallest usable unit.

Each block will need some data to represent each side, for optimization purpose. Let's define an enum to represents sides.

```ts
export enum Side {
  Left     = 1  ,
  Right    = 2  ,
  Forward  = 4  ,
  Backward = 8  ,
  Top      = 16 ,
  Bottom   = 32 ,
  Z_Axis   = 3  ,
  X_Axis   = 12 ,
  Y_Axis   = 48 ,
  All      = 63
}
```

## Chunk definition
A chunk will store different kind of data, including the full and the optimized version of the blocks.

```ts
export type Chunk = {
  position   : Vector3       ; // 3D position in the world
  size       : number        ; // Size of the chunk, default will be 32
  data       : Array<number> ; // The original data
  dataSize   : number        ; // The number of non empty blocks
  rcData     : Array<number> ; // An optimized version of visible only visible data
  rcDataSize : number        ; // The number of visible blocks
  hasRc      : boolean       ; // Define if a chunk has been optimized or not
};
```

## 1D array or the power of flattening everything
When dealing with Typescript / Javascript, it is easy to deal with array of array. It seems common to proceed like this.

But here, we need to keep in mind that performance will decrease rapidly as soon as we add new features, so we need to avoid wasting our precious frame per second by taking the easy way out.

Using a one-dimensional array to simulate a 3-dimensional access will always be faster. We will therefore use functions to simplify our work.

```ts
/**
 * Convert a vector 3 coordinate to a flat array index
 * @param x {number} The x coordinate
 * @param y {number} The y coordinate
 * @param z {number} The z coordinate
 * @param size {number} The size of each dimension, the size is the same for each one
 */
export function vector3ToArrayIndex(x: number, y: number, z: number, size: number = 32) {
  return (size * size * x) + (size * y) + z;
}

/**
 * Convert a flat array index to a 3D coordinate representation
 * @param index {number} The array index
 * @param size {number} The size of x,y,z dimension
 */
export function arrayIndexToVector3(index: number, size: number = 32) {
  return new BABYLON.Vector3(
    (index / (size * size)) >> 0,
    ((index / size) % size) >> 0,
    (index % size) >> 0
  );
}
```

This will conclude our introduction. In the next post, we will see how to render our blocks using Babylon Js, and the minimum of 3D terminology necessary to understand the next posts.

Enjoy !