Terrain

A place's Terrain edited by a program: RobloxFileService opens the place, SmoothGrid decodes the Terrain's SmoothGrid into cells, GetCell and SetCell, the fills, the brushes and ReplaceMaterial change them, and the place is saved with the new bytes.

A worked example

Bay.rbxl is a place whose Terrain is mostly sea, the water's top cells at cell height 3. This program builds a small island there, rock with a grass top and a half-full cell of snow, turns the sea around it to ice, smooths the top and saves the place as BayEdited.rbxl:

use RobloxFileService
use SmoothGrid

local file = RobloxFileService:OpenAsync("Bay.rbxl")
local terrain = assert(file:FindInstance("Workspace/Terrain"))
local grid = SmoothGrid.Decode(terrain.SmoothGrid)
print(grid:GetCell(0, 3, 0))

grid:FillBall(Vector3.new(2, 10, 2), 10, "Rock")
grid:FillBlock(CFrame.new(2, 22, 2), Vector3.new(12, 4, 12), "Grass")
grid:SetCell(0, 6, 0, "Snow", 0.5)
grid:ReplaceMaterial(Vector3.new(-20, 12, -20), Vector3.new(24, 16, 24), "Water", "Ice")
print(grid:GetCell(0, 3, 0))
print(grid:GetCell(0, 5, 0))
print(grid:GetCell(0, 6, 0))
print(grid:GetCell(4, 3, 0))
grid:Smooth(Vector3.new(2, 22, 2), 16, "Grass", 0.5)
print(grid:GetCell(0, 6, 0))

terrain.SmoothGrid = buffer.tostring(grid:Encode())
file:SaveAsync("BayEdited.rbxl")

local saved = RobloxFileService:OpenAsync("BayEdited.rbxl", { ReadOnly = true })
local again = SmoothGrid.Decode(assert(saved:FindInstance("Workspace/Terrain")).SmoothGrid)
print(again:GetCell(4, 3, 0))

shot run Demo.ssp prints

Water 0.625
Rock 1
Grass 1
Snow 0.5
Ice 0.625
Snow 0.43359375
Ice 0.625

and leaves BayEdited.rbxl beside Demo.ssp, with Bay.rbxl as it was. The ball took the sea's cell 0, 3, 0, the block laid grass on cell 0, 5, 0, and Smooth wore the snow down; cell 4, 3, 0 is outside the ball, so its water became ice.

Mime's terrain tools make the same edits to an open Impostor project's Terrain, one undo step a stroke; this page is about programs. Mime's region tools work on a box of cells, as Studio's Terrain Editor does:

  • Replace turns one material into another in the box, or in a ball, block or cylinder; from Air it fills the empty cells.
  • Sea Level fills a region's air with water from its bottom up to a height, and Evaporate takes the water out again.
  • Fill makes every cell of the box one material, and Delete empties it.
  • Copy keeps a region's cells, and Paste lays them with their low corner at a point. The copy's air leaves the cells under it as they are, unless empty cells are pasted too.
  • Generate builds land in the box from biomes (Plains, Hills and Mountains unless others are picked), with caves. The same seed gives the same land, and the box is at most 256 x 64 x 256 cells.
  • Import is Studio's heightmap import: a .png stretched over the box, its left and top edges at the box's low X and Z, replaces what was there with ground as high up the box as each pixel is grey (black the bottom, white the top). It is Grass, or another material picked, unless a second .png, a colormap, gives each pixel the material whose Studio colour is nearest. The box is at most 256 x 64 x 256 cells here too.

Cells

Terrain is a grid of cells 4 studs a side: cell x, y, z covers 4x to 4x + 4 studs on each axis, so cell 0, 3, 0 is the box from 0, 12, 0 to 4, 16, 4. A cell holds one material and an occupancy from 0 to 1, how full it is, kept in 256ths (0.625 is 160/256); Air's is always 0. GetCell(x, y, z) gives a cell's material and occupancy, and SetCell(x, y, z, material, occupancy) sets one (occupancy 1 when left out; Air, or an occupancy of 1/256 or less, empties it). ReadVoxels(min, max) and WriteVoxels(min, max, materials, occupancies) read and write the cells of a box given in studs, snapped outward to whole cells, as [x][y][z] arrays from 1, the way Terrain:ReadVoxels(region, 4) gives them. ReplaceMaterial(min, max, from, to) makes every cell of one material in a box the other: from Air it fills the box's empty cells, to Air it empties them.

The materials are SmoothGrid.MATERIALS: Air, Water, Grass, Slate, Concrete, Brick, Sand, WoodPlanks, Rock, Glacier, Snow, Sandstone, Mud, Basalt, Ground, CrackedLava, Asphalt, Cobblestone, Ice, LeafyGrass, Salt, Limestone and Pavement. A name that isn't one stops the call:

SmoothGrid:SetCell: Lava isn't a terrain material (Air, Water, Grass ... Pavement)

Fills and brushes

The fills are Terrain's, in studs: FillBall(center, radius, material), FillBlock(cframe, size, material) and FillCylinder(cframe, height, radius, material), the cylinder along the cframe's up axis. Air as the material empties the shape.

The brushes are the seven tools of Studio's terrain editor, each one stroke: Add, Subtract, Paint, Smooth, Flatten, Grow and Erode, all called as grid:Add(center, size, material, strength, options). size is the brush's diameter in studs (the editor's Size times 4), strength runs from 0 to 1, and material is the one the brush lays (Subtract ignores it). options is a table, every field optional: Shape ("Sphere", the default, "Cube" or "Cylinder"), Height in studs (the size when left out), PlanePoint (Flatten's level plane is at its height, the centre's when left out), FlattenMode ("Both", "Erode" or "Grow"), IgnoreWater and AutoMaterial, as the editor's checkboxes.

Saving the place

grid:Encode() gives the Terrain's bytes as a buffer, written the way Studio writes them. The SmoothGrid property takes a string, so set it to buffer.tostring(grid:Encode()) and save the place with SaveAsync. On Lune a buffer stops the run:

Failed to convert from 'buffer' into 'BinaryString'

A chunk of the grid (32 x 32 x 32 cells) stays as its bytes until a cell of it is read or written, and a chunk never changed is written back as it came, so decoding and encoding with no edit gives the same bytes. Bay.rbxl's Terrain, 12,810 chunks and 4,623,036 bytes, decoded in 0.3 s on Lune. Each chunk a program reads becomes 64 KB, so read the cells you edit, not a whole map. SmoothGrid.Decode stops the call with why when the bytes aren't a SmoothGrid, and SmoothGrid.new() is an empty terrain.

Limits

  • MaterialColors, the colours of the materials, is a separate property of the Terrain, not part of SmoothGrid. On Lune, reading it stops the run ("Failed to convert from 'MaterialColors' into 'userdata' - Type not supported"), so leave it as the place has it.
  • There is no FillWedge.
  • The fills are the library's coverage rule: a cell within half a cell of the shape is filled by how deep its centre lies in it; a solid raises the cell's occupancy and takes the cell when the centre is at least half a cell deep or the cell was air. Edges can differ from what Terrain:FillBall gives in Studio.
  • The brushes are the editor's per-voxel math, without its pivot or the flood fill it uses for brushes over 32 cells across (those get the same math).

See also

Roblox placesshot import keeps a place's Terrain in <Name>.world/Terrain.rbxm
ServicesRobloxFileService and the other services
Editablesimages, meshes and sounds in memory