voxel¶
Build worlds out of blocks. A voxel world is a component, add it to an entity and fill it with blocks such as grass, stone, water and glass
function setup()
scene.main = scene.default3d()
-- 2 x 1 x 2 chunks, each 16 x 128 x 16 blocks
local island = scene.main:entity("island")
world = island:add(voxel.world, vec3(2, 1, 2))
world:box(vec3(0, 0, 0), vec3(31, 3, 31), "Water")
world:sphere(vec3(16, 0, 16), 10, "Sand")
world:line(vec3(16, 11, 16), vec3(16, 14, 16), "Wood")
world:sphere(vec3(16, 18, 16), 3, "Leaves")
island.position = vec3(-16, -4, -16)
scene.main.camera:add(camera.rigs.orbit).distance = 40
end
Blocks are addressed by integer coordinates local to the world, from vec3(0, 0, 0) up to (but not including) voxel.world.size. Each block fills the unit cube from its coordinate to its coordinate plus one. Coordinates can always be given as a vec3 or as three numbers, and are rounded down to whole blocks
Blocks are named by their type: a block name ("Stone"), a voxel.block or nil for empty space. Any block can also be given a tint color which multiplies its texture
Changes are drawn on the next frame. The world is drawn by child entities of the world’s entity (one per chunk) which use the scene’s lighting and shadows
Large worlds can be generated as they are explored. voxel.world.generate() runs a function from one of your project’s tabs on background threads to fill each chunk as it comes within voxel.world.viewDistance of the camera, and unloads chunks again once they are far away
function setup()
scene.main = scene.default3d()
local terrain = scene.main:entity("terrain")
world = terrain:add(voxel.world, vec3(64, 1, 64))
world.viewDistance = 128
world:generate(asset.Hills, "hills", { height = 12 })
-- Look down on the middle of the world
terrain.position = vec3(-512, -50, -512)
scene.main.camera:add(camera.rigs.orbit).distance = 60
end
function hills(chunk)
for x = chunk.x, chunk.x + chunk.width - 1 do
for z = chunk.z, chunk.z + chunk.depth - 1 do
local top = math.floor(40 + noise(x / 50, z / 50) * params.height)
chunk:box(x, 0, z, x, top - 1, z, "Dirt")
chunk:set(x, top, z, "Grass")
end
end
end
Small voxel models, such as characters and props, are loaded from .cvox files (made with the Voxel Editor) or MagicaVoxel .vox files with voxel.model. Each model is drawn with a regular mesh, which any number of entities can share
function setup()
scene.main = scene.default3d()
-- Frog.cvox from the Froggy example
local frog = voxel.model(asset.Frog)
-- Models are one unit per voxel, scale them down to fit the scene
for i = 1, 3 do
local e = scene.main:entity()
e.mesh = frog.mesh
e.scale = vec3(0.1, 0.1, 0.1)
e.x = (i - 2) * 2 - 0.6
end
scene.main.camera:add(camera.rigs.orbit).distance = 8
end
Worlds and models are drawn with the voxel material, a lit material that shows each block’s texture and tint. Its roughness (0.9), metallic (0), reflectance (0.2), emissive (0, how brightly blocks glow in their own color) and occlusionStrength (0.45, how dark the corners between blocks are) can be changed through voxel.world.material or a model entity’s material. Block types can give their own roughness, metallic and emissive, which are used in place of the material’s
world.material.roughness = 0.6
world.blocks:add("Gold Block", { color = color(255, 200, 80), metallic = 1, roughness = 0.25 })
world.blocks:add("Lava", { texture = asset.builtin.Blocks.Lava, emissive = 2 })
Worlds and models can use any other material too. Shaders read the voxels they draw with these functions, which are available whenever a lit or unlit shader draws a world or model (HAS_VOXELS is defined then)
vec4 getVoxelColor()- the block’s texture tinted by its color. It discards the see-through pixels of cutout blocks and premultiplies translucent colors by their alphafloat getVoxelOcclusion()- how open the corner of the block is, from0(hemmed in by neighbouring blocks) to1float getVoxelRoughness(float fallback),float getVoxelMetallic(float fallback)andfloat getVoxelEmissive(float fallback)- the block type’s settings, orfallbackfor those it doesn’t give
Cutout and translucent blocks are drawn with copies of a world’s material, which follow any changes made to it
stripes = shader.builder()
:lit()
:material
[[
#if defined(HAS_VOXELS)
vec4 color = getVoxelColor();
float stripe = step(0.5, fract(getWorldPosition().y * 2.0));
color.rgb *= mix(0.4, 1.0, stripe) * mix(0.5, 1.0, getVoxelOcclusion());
material.baseColor = color;
material.roughness = getVoxelRoughness(0.8);
material.metallic = getVoxelMetallic(0.0);
material.emissive = vec4(color.rgb * getVoxelEmissive(0.0), 0.0);
#endif
]]
:build()
world.material = material(stripes)
- class voxel.world¶
A fixed size grid of blocks, divided into chunks
Create a world by adding it to an entity with
entity:add(voxel.world, sizeInChunks[, chunkSize]). The world is made ofsizeInChunkschunks, eachchunkSizeblocks in size (vec3(16, 128, 16)by default). It starts out empty, with the standard blocks available invoxel.world.blocksMoving, rotating or scaling the entity moves, rotates or scales the world
local terrain = scene.main:entity("terrain") world = terrain:add(voxel.world, vec3(4, 1, 4)) -- Smaller chunks for a 32 x 32 x 32 world world = terrain:add(voxel.world, vec3(2, 2, 2), vec3(16, 16, 16)) -- Get the world back from its entity later world = terrain:get(voxel.world)
- size: vec3 [readonly]¶
The size of the world in blocks
- chunkSize: vec3 [readonly]¶
The size of each chunk in blocks
- blocks: voxel.blocks [readonly]¶
The block types that can be used in this world
- get(coord)¶
Gets the block at a coordinate
local block, tint = world:get(10, 4, 10) if block then print(block.name) end
- Parameters:
coord (
vec3) – The coordinate of the block (orx, y, z)- Returns:
The block type and its tint, or
nilif the block is empty or outside of the world- Return type:
voxel.block, color
- set(coord, block[, tint])¶
Sets the block at a coordinate. Coordinates outside of the world are ignored
world:set(10, 4, 10, "Stone") world:set(vec3(10, 5, 10), "Solid", color(255, 0, 0)) world:set(10, 4, 10, nil) -- remove a block
- box(min, max, block[, tint])¶
Fills every block between two corners, including the corners themselves
-- A 10 x 1 x 10 floor of grass world:box(vec3(0, 0, 0), vec3(9, 0, 9), "Grass")
- sphere(center, radius, block[, tint])¶
Fills every block within
radiusblocks ofcenterworld:sphere(vec3(16, 8, 16), 6, "Stone") world:sphere(vec3(16, 8, 16), 4, nil) -- hollow it out
- line(start, end, block[, tint])¶
Fills a line of blocks from
starttoend, including both endsworld:line(vec3(0, 10, 0), vec3(20, 15, 8), "Planks")
- viewDistance: number¶
How far (in blocks) from the
voxel.world.focuschunks are generated and drawn when the world has a generator. Chunks a little further away than this are unloaded. Defaults to128
- focus: vec3¶
The point (in scene space) that chunks are generated around, such as the position of a player. When
nil(the default) the scene’s camera is used
- loading: boolean [readonly]¶
truewhile chunks within the view distance are still being generated or built, useful for showing a loading screen
- material: material¶
The material blocks are drawn with, the voxel material unless it’s changed. Cutout and translucent blocks are drawn with copies of it, which follow any changes made to it. Set it to
nilto go back to a new voxel materialworld.material.roughness = 0.4
- generate(tab, functionName[, params])¶
Fills the world using a generator function as chunks come within
voxel.world.viewDistanceof thevoxel.world.focus. Chunks that move out of range are unloaded, unless they have been changed since they were generated. Callinggenerateagain replaces the generator and starts generating from scratchThe function is called with a
voxel.chunkto fill, on background threads so the game keeps running smoothly while the world is generated. Each thread runs the code fromtabin its own Lua environment, so the function can use the code in that tab, the standardmath,stringandtablelibraries,noise,hash,colorandprint, but not globals or functions from your other tabs. Anything else it needs is passed in throughparams, which is copied and available to the generator as the globalparamsmath.randomis seeded for each chunk from its position (andparams.seedwhen it is set), so a world generates the same way every timehash(x[, y[, z]])gives a number from0up to1for a position, which is always the same for that position (andparams.seed), whichever chunk asks for it. Neighbouring chunks can use it to agree on things that cross the edges between them, wheremath.randomwould give each chunk different numbers. Coordinates are rounded down to whole numbers, and neighbouring positions give unrelated numbers (usenoisefor numbers that change smoothly). Give it an extra coordinate for more than one number at a position-- One tree in some 16 x 16 block cells, placed the same way by every chunk that asks function treeIn(cx, cz) if hash(cx, cz, 0) > 0.4 then return nil end local x = cx * 16 + math.floor(hash(cx, cz, 1) * 16) local z = cz * 16 + math.floor(hash(cx, cz, 2) * 16) return x, z end
Blocks can only be changed in chunks that have been generated, changes elsewhere are ignored
world:generate(asset.Generation, "generateTerrain", { seed = 42, rivers = true })
- Parameters:
tab (
asset) – The project tab containing the generator, such asasset.GenerationfunctionName (
string) – The name of the function in that tab to call for each chunkparams (
table) – Values for the generator. Only numbers, strings, booleans and tables of these can be passed
- clear()¶
Removes every block from the world, and stops generating
- place(model, origin, block)¶
Copies a
voxel.modelinto the world as blocks of one type, each tinted with its voxel’s color. The model’s voxel(0, 0, 0)goes atorigin, and its empty voxels leave the world as it is. Placing with anilblock removes the blocks the model covers insteadlocal house = voxel.model(asset.House) world:place(house, vec3(40, 20, 12), "Planks")
- Parameters:
model (
voxel.model) – The model to placeorigin (
vec3) – Where the model’s first voxel goes (orx, y, z)block (
string) – The block name orvoxel.blockto build the model from, ornilto remove blocks
- save(asset)¶
Saves the world’s blocks to a
.cvoxfileA generated world only saves the chunks that have been changed since they were generated, as its generator can make the rest again. This keeps saves small, even for very large worlds
world:save(asset.documents .. "MyWorld.cvox")
- Parameters:
asset (
asset) – Where to save the world, ending in.cvox
- load(asset)¶
Replaces the world’s blocks with those in a file saved by
voxel.world.save()Blocks are matched to the world’s block types by name. A block type the world doesn’t have yet is added without a texture, and can be given one afterwards with
voxel.blocks.add(). The world must have the same chunk size as the one that was saved. Chunks beyond the world’s size are left outTo load a generated world, call
voxel.world.generate()first and thenload. Chunks in the file replace the generated ones, and the generator goes on making the restworld:generate(asset.Generation, "generateTerrain", { seed = 42 }) world:load(asset.documents.MyWorld)
- Parameters:
asset (
asset) – The.cvoxfile to load
- raycast(origin, direction[, distance = 1000])¶
Finds the first block along a ray. The ray is in scene space (the same space as
camera.screenToRay()), so it works however the world’s entity is moved, rotated or scaledfunction touched(touch) if touch.ended then local origin, dir = scene.main.camera:get(camera):screenToRay(touch.x, touch.y) local hit = world:raycast(origin, dir) if hit then -- Place a block on the face that was touched world:set(hit.coord + hit.normal, "Glass") end end end
- class voxel.hit¶
The result of
voxel.world.raycast()- coord: vec3¶
The coordinate of the block that was hit
- normal: vec3¶
The direction of the face that was hit, such as
vec3(0, 1, 0)for the top of a block.coord + normalis the empty space in front of that face.vec3(0, 0, 0)when the ray starts inside a block
- block: voxel.block¶
The type of block that was hit
- color: color¶
The tint of the block that was hit
- point: vec3¶
The point where the ray hit the block, in scene space
- distance: number¶
The distance from the ray’s origin to
point
- class voxel.chunk¶
A chunk of the world being filled by a generator function (see
voxel.world.generate()). Coordinates are world block coordinates, the same asvoxel.world.set(), and anything outside of the chunk is ignored. Blocks are given by name or id, ornilfor empty, and can be followed by a tint color like the world’s own functions. Coordinates are given as numbers, asvec3isn’t available to generatorsfunction generateTerrain(chunk) -- A layer of stone at the bottom of every chunk chunk:box(chunk.x, 0, chunk.z, chunk.x + chunk.width - 1, 3, chunk.z + chunk.depth - 1, "Stone") end
- x: integer [readonly]¶
The x coordinate of the first block in the chunk
- y: integer [readonly]¶
The y coordinate of the first block in the chunk
- z: integer [readonly]¶
The z coordinate of the first block in the chunk
- width: integer [readonly]¶
The size of the chunk along the x axis
- height: integer [readonly]¶
The size of the chunk along the y axis
- depth: integer [readonly]¶
The size of the chunk along the z axis
- get(x, y, z)¶
Gets the name and tint of a block in the chunk
- Returns:
The block name and its tint, or
nilif the block is empty or outside of the chunk- Return type:
string, color
- set(x, y, z, block[, tint])¶
Sets a block in the chunk
- Parameters:
block (
string) – The block name or id, ornilfor emptytint (
color) – A color to tint the block with
- box(x1, y1, z1, x2, y2, z2, block[, tint])¶
Fills every block between two corners, including the corners themselves
- Parameters:
block (
string) – The block name or id, ornilfor emptytint (
color) – A color to tint the blocks with
- class voxel.blocks¶
The block types available to a world. Every world starts with the standard blocks below, which use textures from the builtin
Blocksasset packGrass,Dirt,Sand,Stone,Gravel,Snow,Ice,Bedrock,Water,Glass,Glass Frame,Red Brick,Brick,Coal Ore,Gold Ore,Diamond Ore,Redstone Ore,Planks,Wood,Leaves,Crafting TableandSolid(plain white, use it with a tint)Block types can be looked up by name,
world.blocks.Stoneorworld.blocks["Glass Frame"]- add(name[, definition])¶
Adds a new block type, or changes the existing type with the same name. The definition table can contain:
texture- an image asset used for every face, or a table of assets by facecolor- a tint for every face, or a table of colors by facetranslucent-truefor blended blocks like water and glass. Faces between translucent blocks of the same type are hiddencutout-truefor blocks with see-through holes like leaves, where pixels with less than half alpha are not drawnroughnessandmetallic- from0to1, used in place of the world material’semissive- how brightly the block glows in its own color, from0to8, used in place of the world material’s
Faces are named
all,sides,top,bottom,north,south,eastandwest(most specific wins). Blocks without a texture are plain white, tinted by their colorAny other values in the definition become custom properties of the block type
world.blocks:add("Cactus", { texture = { sides = asset.builtin.Blocks.Cactus_Side, top = asset.builtin.Blocks.Cactus_Top, bottom = asset.builtin.Blocks.Cactus_Inside }, canDig = true }) world.blocks:add("Tinted Glass", { texture = asset.builtin.Blocks.Glass, color = color(120, 200, 255, 160), translucent = true })
- Parameters:
name (
string) – The name of the block typedefinition (
table) – How the block looks, along with any custom properties
- Returns:
The block type
- Return type:
- get(name)¶
Gets a block type by name or id
- Parameters:
name (
string) – The name or id of the block type- Returns:
The block type, or
nilif there isn’t one- Return type:
- all()¶
Gets every block type, in the order they were added
for i, block in ipairs(world.blocks:all()) do print(i, block.name) end
- Returns:
A list of block types
- Return type:
- clear()¶
Removes every block type, including the standard blocks. Blocks already in the world using a removed type are no longer drawn
- class voxel.block¶
A type of block, such as
GrassorWaterCustom values can be stored on a block type, either in its definition or by setting them directly
world.blocks.Bedrock.canDig = false local hit = world:raycast(origin, dir) if hit and hit.block.canDig ~= false then world:set(hit.coord, nil) end
- name: string [readonly]¶
The name of this block type
- id: integer [readonly]¶
A number identifying this block type within its world
- translucent: boolean [readonly]¶
Whether this block type is blended, like water and glass
- cutout: boolean [readonly]¶
Whether this block type has see-through holes, like leaves
- roughness: number¶
The roughness of this block type, from
0to1, ornilto use the world material’s
- metallic: number¶
How metallic this block type is, from
0to1, ornilto use the world material’s
- emissive: number¶
How brightly this block type glows in its own color, from
0to8, ornilto use the world material’s
- class voxel.model¶
A small grid of colored voxels drawn as a single mesh, for things like characters, vehicles and props
Load a model from a
.cvoxor MagicaVoxel.voxfile withvoxel.model(asset.Name), or make an empty one withvoxel.model(size). Voxels are addressed by integer coordinates fromvec3(0, 0, 0)up to (but not including)voxel.model.size, and each one is either empty or filled with a colorcar = voxel.model(asset.Car) -- An empty model with a red voxel in its corner gem = voxel.model(vec3(8, 8, 8)) gem:set(0, 0, 0, color(255, 0, 0))
MagicaVoxel scenes with several objects are combined into one model, using their positions and rotations. Hidden objects and layers are left out, and MagicaVoxel’s z-up axes are turned to match Codea’s y-up
- size: vec3 [readonly]¶
The size of the model in voxels
- mesh: mesh [readonly]¶
A mesh that draws the model, with one unit per voxel and its origin at the corner of voxel
(0, 0, 0). Faces between neighbouring voxels are left out and corners are shaded by ambient occlusion, like avoxel.world. It’s drawn with a new voxel material, or any other material given to its entityThe same mesh is returned until the model changes, so it can be shared by many entities. Changing a voxel makes a new mesh the next time this is read, entities already using the old mesh keep showing it
local tree = scene.main:entity() tree.mesh = treeModel.mesh -- Centered on the entity, standing on its origin local bounds = treeModel.mesh.bounds tree.position = -vec3(bounds.center.x, bounds.min.y, bounds.center.z)
- get(coord)¶
Gets the color of a voxel
- Parameters:
coord (
vec3) – The coordinate of the voxel (orx, y, z)- Returns:
The voxel’s color, or
nilif it is empty or outside of the model- Return type:
color
- set(coord, color)¶
Sets the color of a voxel, or empties it. Coordinates outside of the model are ignored
model:set(2, 0, 3, color(255, 200, 0)) model:set(2, 0, 3, nil) -- remove a voxel
- Parameters:
coord (
vec3) – The coordinate of the voxel (orx, y, z)color (
color) – The voxel’s color, ornilfor empty
- copy()¶
Makes a copy of the model. Changing either one leaves the other as it is, which makes copies useful for undo in an editor. The copy shares the model’s
voxel.model.meshuntil one of them changes, so a copy made after the mesh is read can be shown again without being meshed-- After each edit table.insert(undoSteps, model:copy())
- Returns:
A new model with the same size and voxels
- Return type:
- assign(other)¶
Replaces the model’s size and voxels with another model’s, leaving the other model as it is. Entities keep showing the old mesh until they’re given
voxel.model.meshagain-- Undo, back to the previous step table.remove(undoSteps) model:assign(undoSteps[#undoSteps]) entity.mesh = model.mesh
- Parameters:
other (
voxel.model) – The model to copy from
- save(asset)¶
Saves the model to a file. The file’s extension picks the format:
.cvoxfor the Voxel Editor, or.voxfor MagicaVoxel. MagicaVoxel files can hold at most 255 different colors and 256 voxels in each dimension, larger or more colorful models can be saved as.cvoxgem:save(asset.documents .. "Gem.vox")
- Parameters:
asset (
asset) – Where to save the model, ending in.cvoxor.vox