Voxel Greedy Meshing algorithm not working as intended

Hello! I’ve recently been working on a game that has destruction physics heavily inspired by Jujutsu Shenanigans (not a 1:1 system, but something more that fits my game), however whilst making the meshing algorithm I ran into a few issues that I cannot seem to wrap my head around.

Using code from this post as a base.
I attempted to make it work with my own system by extending it to the Z axis too, however I ran into an issue, instead of it returning a table with all the greedy mesh position data, it simply returns all of the grid positions.

Expected Behavior:
Takes this part (pictured below), splits it into a predefined grids of smaller cubes, gets the cubes touching the hitbox part (not visible here), removes them from the original grid table and then greedy meshes the remaining cubes, and prints out the table of greedy mesh data.

Voxels that should be subtracted from the original grid:

Voxels that should be greedy meshed and placed into the world

Actual Behavior:

Instead of only giving me that parts should be greedy meshed, it spits out all of the grid positions with the same corner1 and corner2 coordinates.

image

I’ve attempted to look through the forums for more information, however I cannot seem to find anything remotely close to my issue, or much on greedy meshing for that matter…

Here is the entire greedy meshing function,
The grid variable is just a dictionary that gets made in another function that splits the part
into smaller cuboids.
Example of some grid data:
A grid has x,y,z positions starting from one corner of a part to the other, with part size and world positions saved too.

local x = 1
local y = 1
local z = 1
local coordinates = x.."-"..y.."-"..z
Grid = {
   coordinates = { Size = vector3.one, Position = CFrame.new(0,0,0)},
}

The greedy meshing code:

function GreedyMesh(Grid)
	local MaxX = 0
	local MaxY = 0
	local MaxZ = 0
	for GridCoordinate, Data in pairs(Grid) do
		local Coordinates = string.split(GridCoordinate,"-")
		if tonumber(Coordinates[1]) > MaxX then
			MaxX = tonumber(Coordinates[1])
			local Size = Data.Size
		end
		if tonumber(Coordinates[2]) > MaxY then
			MaxY = tonumber(Coordinates[2])
			local Size = Data.Size
		end
		if tonumber(Coordinates[3]) > MaxZ then
			MaxZ = tonumber(Coordinates[3])
			local Size = Data.Size
		end
	end
	local IsVisited = {}
	local GreedyMeshes = {}
	local function IsEmpty(Grid,GridCoords)
		return Grid[GridCoords] == nil
	end
	for X = 1, MaxX, 1 do
		for Y = 1, MaxY, 1 do
			for Z = 1, MaxZ, 1 do
				local StartX = X
				local StartY = Y
				local StartZ = Z
				local EndX = X
				local EndY = Y
				local EndZ = Z

				local Coordinates = X.."-"..Y.."-"..Z

				IsVisited[Coordinates] = true

				while EndX < MaxX do
					local NewEndX = EndX + 1

					local Coordinates = NewEndX.."-"..Y.."-"..Z

					local IsUseable = not IsVisited[Coordinates] and not IsEmpty(Grid,Coordinates)

					if not IsUseable then
						break
					end

					IsVisited[Coordinates] = true
					EndX = NewEndX
				end
				while EndY < MaxY do
					local NewEndY = EndY + 1

					local IsRowUseable = true

					for dx=StartX, EndX do
						local Coordinates = dx.."-"..NewEndY.."-"..Z
						local IsUseable = not IsVisited[Coordinates] and not IsEmpty(Grid,Coordinates)
						if not IsUseable then
							IsRowUseable = false
							break
						end
					end

					if not IsRowUseable then
						break
					end

					for dx=StartX, EndX do
						local Coordinates = dx.."-"..NewEndY.."-"..Z
						IsVisited[Coordinates] = true
					end 
					EndY = NewEndY
				end  
				while EndZ < MaxZ do
					local NewEndZ = EndZ + 1

					local IsRowUseable = true

					for dx=StartX, EndX do
						local Coordinates = dx.."-"..EndY.."-"..NewEndZ
						local IsUseable = not IsVisited[Coordinates] and not IsEmpty(Grid,Coordinates)
						if not IsUseable then
							IsRowUseable = false
							break
						end
					end

					if not IsRowUseable then
						break
					end

					for dx=StartX, EndX do
						local Coordinates = dx.."-"..EndY.."-"..NewEndZ
						IsVisited[Coordinates] = true
					end 
					EndZ = NewEndZ
				end      
				table.insert(GreedyMeshes, {
					Corner1 = StartX.."-"..StartY.."-"..StartZ,
					Corner2 = EndX.."-"..EndY.."-"..EndZ
				})
			end
		end
	end
	
	print(GreedyMeshes)
	print(#GreedyMeshes)
end

If you have any questions as to how any of the code / systems work, feel free to ask.

2 Likes

Here’s a example based off this video: https://www.youtube.com/watch?v=L6P86i5O9iU&t=220s that works in 3d. I used a modified version of this script in my old voxel destruction module. Its pretty fast and if you mess around with it you should be able to get it to work with different part sizes and such, or whatever fits your greedy meshing needs.

local blocks = {}
local sizes = {}

local colors = {
	Color3.new(.24, .67, .05),
	Color3.new(.52, .29, .08),
	Color3.new(.43, .38, .35)
}

local amount = 8

for x = 1, amount do
	blocks[x] = {}
	sizes[x] = {}
	
	for y = 1, amount do
		blocks[x][y] = {}
		sizes[x][y] = {}
		
		for z = 1, amount do
			blocks[x][y][z] = math.random(1, #colors)
			sizes[x][y][z] = {["X"] = 1, ["Y"] = 1, ["Z"] = 1}
		end
	end
end

for x, row in pairs(sizes) do
	for y, column in pairs(row) do
		for z, size in pairs(column) do
			if not sizes[x][y][z] then continue end
			if not sizes[x][y][z - 1] then continue end
			if blocks[x][y][z] ~= blocks[x][y][z - 1] then continue end
			sizes[x][y][z].Z += sizes[x][y][z - 1].Z
			sizes[x][y][z - 1] = nil
		end
	end
end

for x = 2, amount do
	for y, column in pairs(sizes[x]) do
		for z, size in pairs(sizes[x][y]) do
			if not sizes[x][y][z] then continue end
			if not sizes[x - 1][y][z] then continue end
			if size.Z ~= sizes[x - 1][y][z].Z then continue end
			if blocks[x][y][z] ~= blocks[x - 1][y][z] then continue end
			size.X += sizes[x - 1][y][z].X
			sizes[x - 1][y][z] = nil
		end
	end
end

for x, row in pairs(sizes) do
	for y = 2, amount do
		for z, size in pairs(sizes[x][y]) do
			if not sizes[x][y][z] then continue end
			if not sizes[x][y - 1][z] then continue end
			if size.Z ~= sizes[x][y - 1][z].Z then continue end
			if size.X ~= sizes[x][y - 1][z].X then continue end
			if blocks[x][y][z] ~= blocks[x][y - 1][z] then continue end
			size.Y += sizes[x][y - 1][z].Y
			sizes[x][y - 1][z] = nil
		end
	end
end

for x, row in pairs(sizes) do
	for y, column in pairs(row) do
		for z, size in pairs(column) do
			local part = Instance.new("Part")

			part.Anchored = true
			part.Position = Vector3.new(x - size.X / 2, y - size.Y / 2, z - size.Z / 2)
			part.Size = Vector3.new(size.X, size.Y, size.Z)
			part.Color = colors[blocks[x][y][z]]
			part.Parent = workspace
		end
	end
end
2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.