Minimalist multipurpose blocky voxel engine API design documentation. With blocky we mean Minecraft/Teardown style.
There are some good voxel engines out there, but those all seem to be very restrictive in terms of customization and come with large code bases that you need to understand and hack in order to add custom functionality. I want to propose a minimalistic and generalized API that a voxel engine could provide/expose for advanced voxel game/sandbox developers.
Ideally witten in C so can be used as header in HLSL/GLSL/C++/C and beyond (inspired by https://github.com/AcademySoftwareFoundation/openvdb/blob/master/nanovdb/nanovdb/PNanoVDB.h). Neverless some parts belong to CPU only, for othes it depends on the implementation. The tehnical imlementation details are out of the scope of this document.
Camera {
float3 position,
float3 rotation,
float viewDistance,
float fov,
int3 partitionCoords
}
viewDistanceengines uses this to calculate when to stream in/out partitions. If camera position + view distance is out of current partition (in any direction) and in next one, it should be streamed in. And via versa.fovfield of view.positioncurrent position of camerarotationcurrent rotation of camera in euler anglespartitionCoordspartition coordinates where camera resides at this moment
Top view for illustration purposes only:

Partitions are cubic shaped grids that form up into a larger uniform grid and are streamed in and out on demand (think of Unreal World partitions, but 3D). A partition where the player resides and it's neigbour partitions make up the space that player should be able to observe and where voxel manipulations/events are possible. The goal is to always have 26 neighbour partitions around current one.
Partition {
int3 position
}
positionx, y, z coordinates of the partition.
Voxel {
// generic properties here - primitives only
float3 color,
float hardness
}
The data here is generic. Whatever is needed for certain purpose. The structure will be the same for all voxels and has do be declared before runtime. More properties, larger the map in MB. All possible varinations have to be registered (see in fallowing section "Registers").
Group {
// generic properties here - primitives only
float damage,
float3 velocity
}
The data here is generic. Whatever is needed for certain purpose. The structure will be the same for all groups and has do be declared before runtime. More properties, larger the map in MB.
VoxelAccessor {
int3 position,
int lod,
Voxel voxel
}
positionvoxel index position in the gridloddepth of voxelvoxelstructure described above
This is object you use for updating and querying voxel.
VoxelsSelector {
VoxelAccessor[] getAll(),
VoxelAccessor[] slice(uint from, uint to),
VoxelAccessor[] splice(uint from, uint to, VoxelAccessor va = null),
}
getAll()getter for all voxel accessorsslice(uint from, uint to)take a piece of voxel accessorssplice(uint from, uint to, VoxelAccessor va = null)insert, replace or remove voxel accessor(s) // concept from JavaScript splice
This is object is used to point to multiple voxels.
GroupAccessor {
float3 position,
float3 rotation,
int voxelCount,
Group group
}
positioncurrent position of grouprotationcurrent rotation of group in euler anglesvoxelCounttotal voxel count in groupgroupstructure described above
This is object you use for updating and querying group.
Collider {
impactPoint,
voxelAccessor
}
Object holding info about collision.
impactPointlocal hit pointvoxelAccessorvoxel colliding
Hit {
float3 localPointOnVoxel
VoxelAccessor voxel
float length
}
localPointOnVoxellocal hit pointvoxelvoxel being hitlengthray total length
Engine can make impressive optimziations if it knows all possible variations of the voxel generic data. There for we have function:
registerVoxelProperties<T = propery of Voxel>(keyof T propery, valueof T[] values)
properygeneric property likecolorvaluesgeneric property value like{ float3(1,0,0), float3(1,1,0) }
NOTE: This is not optional.
changeSettings(int gridPartitionSize, int minVoxelSize, int maxTreeDepth, Enum accelerationStructure = null)
This should be called somwhere in game initialization phase to provide these important settings to the engine.
gridPartitionSizeis the size of a single partitionminVoxelSizehow small is the smallest voxel. This will have major impact on performance and visual appealmaxTreeDepthhow many lod levels each partition grid tree (acceleration structure) will haveaccelerationStructureengine can provide multiple acceleration structures, like octees, brickmaps, sparse brick sets, etc. If you change this after saving a map, you wont be able, most likely to load it, unless there is some centralized storage format.
camera = createCamera(float viewDistance, float fov)
Defining the camera.
viewDistancedescription in defintionsfovdescription in defintions
transformCamera(Camera camera, float3 position, float3 rotation)
Transforming the camera. This can trigger streaming of partition(s) dependent on position and view distance.
cameracamera objectpositiondesired position of camerarotationdesired rotation of camera in euler angles
onGridChunkStream(Partition partition)
Event fired whenever engine needs to stream (in/out) a partition. Can be used of procedural generation or other purposes.
partitiondescription in defintions
Partition[] partitions = getAllActivePartitions(int3 coords = null)
Get all stramed in or streaming in paritions at this moment.
coordsif provided, will return 1 respestive partition or null if its not active
selectPartition(int3 coords)
We select a one of the Streamed in paritions, to work with. All the things below will take place in this selected partition and its local coordinates. It should throw error, if parition is not active streamed in.
coordspartition global 3d coordinates
VoxelsSelector voxelsSel = setVoxels(int3[] gridCellCoords, Voxel voxel)
Spawns block voxel with all its properties in selected area, all of it. Returns those new voxels.
gridCellCoordsgrid cell coordinates where to spawn voxelvoxeldescription in defintions
VoxelsSelector voxelsSel = selectVoxelInRectArea(int3 start, int3 end)
Get all voxels in rectangular area.
startstar position in grid, or bottom left cornerendend position in grid, or top right corner
GroupAccessor group = groupVoxels(VoxelsSelector voxelsSel)
Registers a voxel group. This very usefull to represent sort of a Entity, like a rock, that is composed from multiple voxels.
Group can never be larger than one parition.
GroupAccessor group = getVoxelGroup(VoxelAccessor voxel)
Check/get if voxel is in a group.
VoxelsSelector voxelsSel = getGroupVoxels(GroupAccessor group)
To get all voxels in a group. Voxels belonging to a partition that is not yet streamed in wont be returned. This can be checked by 'GroupAccessor.voxelCount'
transform(GroupAccessor group, float3 position, float3 rotation)
Transform group by given position and rotation.
groupvoxel group accessorpositiondesired position for grouprotationdesired rotation for group in euler angles
changeProperty<T = propery of Voxel>(VoxelAccessor voxel, keyof T property, valueof T value)
changeProperty<T = propery of Voxel>(VoxelsSelector voxelSel, keyof T property, valueof T value)
changeProperty<T = propery of Group>(GroupAccessor group, keyof T property, valueof T value)
Change properties of an existing voxel or group of voxels
voxels/voxelSel/groupvoxel accessor, voxel selection or voxel group accessorpropertygeneric propertyvaluegeneric property value
onCollision(Collider c1, Collider c2){ // Colliders for both voxels, description is in defintions
float3 impactPoint1 = c1.impactPoint
VoxelAccessor va2 = c2.voxelAccessor
// code the collision reaction your self, dependent on properties etc.
}
Event fired whenever 2 voxels collide.
Hit hit = rayCast(float3 origin, float3 direction, float3 lenngth = null, uint lod = null)
Raycast voxels in streamed in partitions. Must be as efficient as possible, because will be probably used for rendering.
originorigin of the raydirectiondirection to shoot the raylenngthoptional length for ray, by default its view distancelodLeveloptional level of detail, by default is accepts leaf voxels. More coarse voxels from tree stracture can be fetched by providing depth level here. Its properties are interpolated from child voxels.
save(str file, int3 partitionCoords)
Store parition.
filefile destinationpartitionCoordspartition coords to store
load(str file, int3 partitionCoords)
Manually load some parition.
filefile destinationpartitionCoordspartition coords to load
Engine takes care of acceleration structure creation and modification, compression, partition stream in/out, raycast by lod, etc. Also speculatively deactivates and activates colliders depenging on actions in the parition.
Programmer uses all of this to create own voxel game logic and experience. No Biased opinions on how physics should work, what renderer to use (taster/raytrace/pathtrace) and so on.
No. Not at all. You can use the Raycast for such purposes, but the rest is for you to implement.
No. Not at all. Only collisions, but the rest is for you to implement.
We can fast access voxels that make up some kind of a entity, like a door, rock, etc.
As you can see lots of voxe engines (Teardown, The Sandbox, Atomontage, etc.) are already doing this. Thencial details are not part of this document.
Think of Minecraft, Teardown, etc. Blocks.
It is essential for procedural generation. And also for coordination system. Each partition has local one. If we use global coordinates, the numbers can get insanely large to impossible.