How to find and fill in empty spaces in a 2D frame made of cubes?

Hello, I am new to the developer forum. If I make any mistake, please tell me.

So I have this frame made of cubes. Once I complete the frame, how can I find the empty spaces in the frame and fill it in with more cubes like the cube farming mechanic in Antichamber?

The position of the cubes are stored in a 3D array.

Here is an example of what I am trying to make:
Incomplete frame

Complete frame

Automatically filling it in

Note that I also want this to work with all kinds of shapes, not just rectangles.
Examples of irregular shapes:

Any help would be appreciated!

You want a “flood fill” algorithm, which is pretty readily available online.

If you shared specifics about how your data is stored and how you want the mechanics to work, people might be able to help more.

1 Like

Thank for your reply. This seems like a really good solution. But there is one more problem. How can I find the position to start the flooding? Because I want it to start filling in immediately after the frame is completed.

Btw here is the code that manages cube data storage

-- The table that contains the cubes
CubeList = {}

-- Returns the cubes at the specified position, or nil if it does not exist
function Data.Cube.Get(x:number, y:number, z:number):{Part}
	local CubeX = CubeList[x]
	if CubeX then
		local CubeY = CubeX[y]
		if CubeY then
			local CubeZ = CubeY[z]
			if CubeZ then
				return CubeZ
			else
				return nil
			end
		else
			return nil
		end
	else
		return nil
	end
end

-- Set the position in the table with a newly created cube
function Data.Cube.Set(cube:Part, x:number, y:number, z:number)
	local CubeX = CubeList[x]
	if not CubeX then
		local NewX = {}
		CubeList[x] = NewX	
		CubeX = NewX
	end
	local CubeY = CubeX[y]
	if not CubeY then
		local NewY = {}
		CubeX[y] = NewY	
		CubeY = NewY
	end
	local CubeZ = CubeY[z]
	if not CubeZ then
		local NewZ = {}
		CubeY[z] = NewZ
		CubeZ = NewZ
	end
	table.insert(CubeZ, cube)
end

-- Remove the cubes at the specified position from the table
function Data.Cube.Remove(x:number, y:number, z:number)
	local CubeX = CubeList[x]
	if CubeX then
		local CubeY = CubeX[y]
		if CubeY then
			local CubeZ = CubeY[z]
			if CubeZ then
				CubeY[z] = nil
				-- GetNumberOf() is a function that checks the number of objects in the table. 
				-- I'm not using # to get the number of values in the table because it's technically a dictionary
				if GetNumberOf(CubeY) == 0 then
					CubeX[y] = nil
					if GetNumberOf(CubeX) == 0 then
						CubeList[x] = nil
					end
				end
			end
		end
	end
end

I see. Do you have any constraints on your system that can help us narrow down an algorithm?

For example, is there a maximum size the grid might be, and do you know where the edges are?

Is this grid arbitrary size and shapes can be arbitrarily large?

What defines a shape as “closed”? How do you even define flat shapes in a 3D grid?

Because the cube can exist anywhere in the world, there is no size limit to the grid. The shapes can be as big as the player want to make them, but I will probably add a limit to it later so big shapes wouldn’t lag the game.

To define flat shapes on a 3D grid, I can focus on only two axis. For example, the X and Z axis and ignoring the Y axis for shapes made on the floor.

It’s kind of hard to explain a shape being “closed”, but I’ll try my best.
A “closed” shape is a shape where all the corners filled in. Every cube that makes up the frame must have at lease 2 cubes next to it in the up, down, left, right direction. Diagonals does not count.

Also, this might make it more complicated but if there are other cubes connected that are not needed to form the frame, ignore them

I can think of one possible algorithm:

  1. Whenever a new cube is placed…
  2. Find an “outside cube” to start with. Possible ways:
    • Pick any empty spot on the outside of the placement area, or
    • start at a spot far away from the placed cube that’s guaranteed to be empty, and march inwards until you hit the cube you just placed, then back up one to the last empty cube.
  3. From that “outside cube”, run a flood fill to mark all the “not inside a shape” cubes with some marker (not a cube, mind you, just a little tag that tells you they’re outside)
  4. Any cubes that you didn’t just tag, and don’t already have a cube in them, put a cube in.

The biggest issue is maybe step 3. If your placement area is super large, that might take a while. It would have to be pretty massive though, and you can always stop iterations at a certain point to get “good enough”.

I can come up with an example implementation if you like.

Edit: Thinking about this more… Step (2) might need to be modified because your first “outer cubes” flood fill might miss cubes on the outside that are partially walled off by interior cubes. So your flood fill algo might have to start with all the open border cubes in the queue.

1 Like

Thank you for your help! It works!

I did have to modify a couple of things.

In step 2, I changed the way to find the “outside cube”. Once a new cube is placed, instead of picking a random spot far away and marching in, I first find all the connecting cubes. Then find its bounding box by comparing each cube’s position to see which one is the highest/lowest on each axis. After that, I created a “barrier” that is one stud bigger than the shape bounding box. The “outside cube” will be located in the bottom left corner of the barrier.

This will ensure that the “outside cube” will always be empty and all missing spaces outside of the frame is covered.

This also helps with lag in step 3, as it does not have to iterate a huge area.

During the flood fill in step 3, all cubes that are not part of the main structure is ignored and tagged to prevent interference.

Lastly, I checked for empty spaces that didn’t get tagged and filled it in with cubes like you said.

Here is a showcase of what it can do:

Again, thanks a lot for your help. I would have been stuck on this part for a long time without your help.

1 Like

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