This post will show you how to make a 2D(easily extendable to 3D) greedy mesher for all your voxel optimization needs(i would not recommend visible faces only for this as it runs slower than all in my testing). So let’s start by making a heightmap. But inside of that we will also make a table of whether or not it has been visited before(you’ll see why later).
Make the map
Okay so let’s start by defining the tables. For your height map, you can use any function you want, if its math.noise
or just if the y < 20
, it won’t make a difference. For now though, we will just use math.sin
local isVisited = {}
local map = {}
We’ll have them named like that. So lets loop through every x and y.
for x = 0,50,1 do
for y = 0,50,1 do
end
end
Now let’s make map[x][y] = 0 or 1
(block or air), and we will have isVisited[x][y] be false, since we haven’t visited it yet.
for x = 0,50,1 do
map[x] = {}
isVisited[x] = {}
for y = 0,50,1 do
map[x][y] = y > math.sin(x) * 25 and 0 or 1
isVisited[x][y] = false
end
end
Ok, now we can move on to the actual mesher!
The Greedy Mesher
Okay, so for the greedy mesher we’ll need these variables, and a utility function. isEmpty
checks if block ~= 0
, cause we remember that 0
is a block, and 1 is air.
local cuboids = {}
local size = 50
local function isEmpty(block)
return block ~= "0"
end
Okay, now let’s get into the mesh loop.
for x = 1, size do
for y = 1,size do
local startX = x
local startY = y
local endX = x
local endY = y
isVisited[x][y] = true
end
end
That should be fine. Now the actual greedy mesher!
Repeat checking if the block isVisited
or the block is empty, and if both are false
, then we can move the endX
on by 1! This should be nested inside of the for loop.
while endX < size do
local newEndX = endX + 1
local isUseable = not isVisited[newEndX][y] and not isEmpty(map[newEndX][y])
if not isUseable then
break
end
isVisited[newEndX][y] = true
endX = newEndX
end
Okay, now we can do the same for the Y except when we push up the Y, we have to go through all the blocks on the line to make sure they work. This should be right after the X loop
while endY < size do
local newEndY = endY + 1
local isRowUseable = true
for dx=startX, endX do
local isUseable = not isVisited[dx][newEndY] and not isEmpty(map[dx][newEndY])
if not isUseable then
isRowUseable = false
break
end
end
if not isRowUseable then
break
end
for dx=startX, endX do
isVisited[dx][newEndY] = true
end
endY = newEndY
end
-- this inserts it
table.insert(cuboids, {
startX = startX,
startY = startY,
endX = endX,
endY = endY
})
Now all we have to do is generate it. But I’ll leave that to you.