WP Ad Inserter plugin for WordPress

How I made multi-threaded voxel engine in TypeScript

Read Time:4 Minute, 3 Second

How I made multi-threaded voxel engine in TypeScript

Hello, I am Luke the creator of Divine Voxel Engine.

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:

 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; }
Enter fullscreen mode Exit fullscreen mode

Then we get the relative voxel position in the chunk:

 getVoxelPositionFromChunkPosition( x: number, y: number, z: number, chunkPOS: Position3Matrix ) { this.__voxelPosition.x = Math.abs(x - chunkPOS.x); if (x < 0) { if (x == chunkPOS.x + ((1 << 4) - 1)) { this.__voxelPosition.x = (1 << 4) - 1; } } this.__voxelPosition.z = Math.abs(z - chunkPOS.z); if (z < 0) { if (z == chunkPOS.z + ((1 << 4) - 1)) { this.__voxelPosition.z = (1 << 4) - 1; } } this.__voxelPosition.y = Math.abs(y - chunkPOS.y); if (y < 0) { if (y == chunkPOS.y + ((1 << 4) - 1)) { this.__voxelPosition.y = (1 << 4) - 1; } } return this.__voxelPosition; }
Enter fullscreen mode Exit fullscreen mode

Then we get the index of the voxel in the chunks data:

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; }
Enter fullscreen mode Exit fullscreen mode

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

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
Sun Light
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. 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.

Source: https://dev.to/lucasdamianjohnson/how-i-made-multi-threaded-voxel-engine-in-typescript-1e8f

How I build a Documentation site using Docz Previous post How I build a Documentation site using Docz
Revealing Compound Types in Typescript Next post Revealing Compound Types in Typescript

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.