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.