How I made a multi-threaded voxel engine in TypeScript
Tags: typescript, webdev, gamedev, javascript
Published: 2022-11-10 15:11:33


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jngu0w1m2bf6fii2ps9m.jpeg)

Hello, I am Luke the creator of [Divine Voxel Engine](https://github.com/Divine-Star-Software/DivineVoxelEngine). 

Divine Voxel Engine (DVE) is a truly multi-threaded voxel engine written in TypeScript that uses Babylon.Js. 

In this post I just want to go over on a high level how it works. 

## Chunk Data And Shared Array Buffers
  The engine stores the data for voxels in chunks. Chunks   are segments of the world. By default they are defined as 16mx16mx16m areas. They are stored in a multi-level hash map. The first level being a region (512mx512mx512m area), then the next being a column (same width and depth as a chunk but as tall as a region). 
  The chunk data itself is just a `SharedArrayBuffer`. The engine uses the `DataView` object to read and write data to it. The buffer has a few headers such as its' position and height map. 
  **The buffer is shared with all threads (workers) that need it. This way chunk building, water flow, light updates, and much more can be done in parallel**.
## Data Indexing 
Since the data for the chunk is basically stored in a flat array we need to get the index for it ourselves. 

The next code snippets are part of objects in the engine. 

The first thing we do is get the chunk position:
```ts
 getChunkPosition(x: number, y: number, z: number) {
  this.__chunkPosition.x = (x >> 4) << 4;
  this.__chunkPosition.y = (y >> 4) << 4;
  this.__chunkPosition.z = (z >> 4) << 4;
  return this.__chunkPosition;
 }
```

Then we get the relative voxel position in the chunk:
```ts
 getVoxelPositionFromChunkPosition(
  x: number,
  y: number,
  z: number,
  chunkPOS: Position3Matrix
 ) {
  this.__voxelPosition.x = Math.abs(x - chunkPOS.x);
  this.__voxelPosition.z = Math.abs(z - chunkPOS.z);
  this.__voxelPosition.y = Math.abs(y - chunkPOS.y);
  return this.__voxelPosition;
 }
```

Then we get the index of the voxel in the chunks data:
```ts
const bounds = {
  x : 16,
  y : 16,
  z : 16
};
 export function getIndex(x: number, y: number, z: number) {
  return x + y * bounds.x + z * bounds.z * bounds.y;
 }
```

 
## Voxel Data
Voxel data is stored in 4 bytes. 
The first two bytes are for light data. Light is stored as 4 bits for each light type. Light types include sun light and RGB light. 
The next two bytes are for storing the voxel numeric id. After that the last bytes are for storing voxel shape state and voxel level as well as secondary voxel id for dual voxels (think of a plant underwater). 
## Mesh Building 
All mesh building is done in the `Constructor` thread. A chunk is sent into a processor which will produce a template. The template is simply a list of all the exposed faces, the lighting gradient (and other gradients), texture uvs, and the actual voxel shapes themselves. 
This template is then sent to a mesher which produces the actual mesh data for the chunk. Once it is done it just sends it to the render thread. 
If you want to see how the light gradient and ambient occlusion is calculated check this out:
[CalculateVoxelLight](https://github.com/Divine-Star-Software/DivineVoxelEngine/blob/main/src/Constructor/Builder/Processor/Functions/CalculateVoxelLight.ts)
## Light Updates
Lighting for the voxel engine uses a simple breadth first search algorithm. If you want to see the code directly here it is:
[RGB light](https://github.com/Divine-Star-Software/DivineVoxelEngine/blob/main/src/Constructor/Propagation/Illumanation/Functions/RGBLight.ts)
[Sun Light](https://github.com/Divine-Star-Software/DivineVoxelEngine/blob/main/src/Constructor/Propagation/Illumanation/Functions/SunLight.ts)
Sun light is a little different than RGB light. Since sun light does not deplete as it goes downward through air while RGB light does. 
## Inter Thread Communication
The engine is stitched together with another library I wrote called [ThreadComm](https://github.com/Divine-Star-Software/ThreadComm). It makes it easy to communicate between  many threads and is set up to work in both the browser and the node environment. 
`ThreadComm` allows you to create tasks queues which are basically functions in other threads which are executed from another thread and have a call back once all the tasks for that queue are finished. 
The engine does not use one big queue for every update. It splits updates into regions. So, updates far away from one another will not be affected. 

## Conclusion 
This was just a very high level overview of how DVE works. If you would like to know more or have specific questions let me know. DVE is licensed under MIT so you can use it for any project. However, all assets for the test worlds are owned and copyrighted by me.