Grid system room detection

I am creating a game in which the construction goes on a three-dimensional grid, there is a block that blocks all the faces of the cell, there is also a wall that blocks one of the side faces of the cell and so on. The goal is to define an enclosed space (room) that is formed from the blocked faces of the grid cells

1 Like

Depends greatly on how you’ve structured your system.

First off, do you keep track of all the blocks? Or the system you’ve made just place them down and then just erases trace of them?
A first step would be ensuring there’s some track of them.

If you’re already keeping track of them and if you’re able to map them out in a 3D cartesian plane, you may refer to this topic in StackExchange to find out whether ant given point is within bounds in a 3D cartesian plane: How to check if point is within a rectangle on a plane in 3d space - Mathematics Stack Exchange

Yes, I save my blocks to a table

function ConstructSystem.new(ModelName: string): Construction
	local ConstructData = Items.Constructions[ModelName]
	
	if not ConstructData then warn(`[{script.Name}] Cant find model with name: {ModelName}`); return end
	
	local self = setmetatable({}, ConstructSystem)
	
	self.Model = ConstructData.Path:Clone()
	
	self:InitBlock()
	self:ChangePhase(ConstructData.CurrentPhase or 1)

	table.insert(AllConstuctions, self)

	return self
end

function ConstructSystem:Place(Position: Vector3, RotationVector: Vector3)
	if not self.Model then warn(`[{script.Name}] Cant find model with name: `, self); return end
	
	self.Position = Position or self.Position
	self.RotationVector = RotationVector or self.RotationVector or Vector3.zero
	
	self.Model.Parent = workspace.Constructions
	self.Model:PivotTo(CFrame.new(self.Position))
end

What are you trying to achieve?

if you want to make a box room out of the blocks get the size of the room box minus the interior blocks

Looks solid.
Try to apply what the link to StackExchange I shared in my past post directs, perhaps it can help you since I see you’re pivoting these Models to their position, which is a good base for a system like that.
To determine which point to check each time, you can take all the sides of a Model upon being placed, and if you find that the point is within bounds, then it’s an enclosed space.

Pretty sure they’re trying to detect the occurrence of enclosed spaces in their placement system (like, be able to check/determine with every new block if an enclosed space has been created).

I checked it but dont understand at all

It looks intimidating at first, however, it’s quite easy once you get the hang of it. I’ll try my best to explain.
Each letter represents a point in the plane, so, given the plane:

where (b , c , d , e) are the vertices of a rectangle, and a is a standalone point.

As a preamble, ‘scalar product’ (also known as ‘dot product’) is an operation that, given two vectors (under this context, but, in theory, it could be any kind of numerical set) returns a scalar number with diverse properties we can apply.

Moving on, to check whether point a is inside the bounds of the rectangle (b , c , d , e) we need to do the following:

which, in pseudo-scripting would be something like this (supposing all the letters are points in the 3D cartesian plane):

if ( (DotProduct(b, (c-b)) <= DotProduct(a, (c-b)) <= DotProduct(c, (c-b)) )
and
( DotProduct(b, (e-b)) <= DotProduct(a, (e-b)) ) <= DotProduct(e, (e-b))
then a is within the bounds of rectangle bcde

Applied to Roblox-Luau, to calculate the dot product we have Vector3:Dot() , which calculates the dot product between the Vector3 called on and the Vector3 that’s passed as a parameter.
The above pseudo-code would be applied like something like this (supposing a, b, c, d, e are all Vector3):

if ( b:Dot(c-b) <= a:Dot(c-b) ) and ( a:Dot(c-b) <= c:Dot(c-b) ) and ( b:Dot(e-b) <= a:Dot(e-b) ) and ( a:Dot(e-b) <= e:Dot(e-b) ) then
  -- point a is within bounds b,c,d,e
end

we can furtherly clean that code by doing something like this (will look longer, but it’s more readable):

local cbDifference = c-b
local ebDifference = e-b
if ( b:Dot(cbDifference) <= a:Dot(cbDifference) ) and ( a:Dot(cbDifference) <= c:Dot(cbDifference) ) and ( b:Dot(ebDifference) <= a:Dot(ebDifference) ) and ( a:Dot(ebDifference) <= e:Dot(ebDifference) ) then
  -- point a is within bounds b,c,d,e
end

Hope that helps.

1 Like

What is a and why do I need it to identify rooms, sorry for such a question. I want to make a system that recognizes rooms formed from building blocks, such as foundations, walls, and so on. Since the construction is going on an infinite grid of 8x8x8 in size, I thought I could use this to write to the table which faces of the grid are blocked by blocks and then using some algorithms to determine the closeness to find this space. Rooms can be of any shape, and rooms can also have common faces, for example, the same wall is a common face in both rooms. As a result, I want to make the algorithm find enclosed spaces and return a certain logical model of the room, which has the components of the room - cells 8x8x8, room volume, temperature, and so on. :grinning:

You can use Chunk system to place blocks as chunks, then check all chunks around and see if they are full, if they are then you determined closed space

a would be the point in space you want to check whether or not is in a confined space (it’s a Vector3, in this case). meanwhile b, c, d, e would be the locations of the nearby Models (if any) to determine whether they form an enclosed space.

You’re taking it as a point to do the calculations of whether it’s within an enclosed space or not (that formula checks for the enclose-ness of a specific point, while it doesn’t directly check for the enclosed space itself).

It’s completely fine, don’t worry.

I’ll try to develop something to show you that way.
In the meantime, here’s a dev-talk with the developers of RimWorld where they discuss how they handle regions internally, and there are multiple mentions and use of enclosed spaces (I haven’t seen it all, that’s why I’m unsure if they really mention how they did it, but it can give you a good idea of how it’s done): https://youtu.be/RMBQn_sg7DA?si=WmP1doqElgWru6jP (around 16:25 they use an enclosed room, and 25:56 as well, for instance).

I provided you, @range_0xE4 , a solution in my past posts; I realized that since you want a 3D space, those comparisons were going to be quite expensive to perform.
Therefore, I propose to you a simple 3D flood-fill algorithm to find enclosed spaces (spaces without contact with the rest, which may be referred to as ‘islands’).

Credits to this post and its author:

for implementing a 2D flood-fill algorithm into Roblox-Luau, which served as the base for my 3D implementation of it:

Video (at the end of the simulation, the black blocks represent the enclosed spaces or ‘islands’):


Script:

local WorkSpace = game:GetService("Workspace")

local baseVector = Vector3.new(8, 8, 8) -- 8-base, since each block is 8x8x8.
local size = 40 -- lower this if in a low-end device.
local perlinDivisor = 5

local gridFolder = Instance.new("Folder")
gridFolder.Name = "Grid"
gridFolder.Parent = WorkSpace

local function generateGrid()
	local grid = {}
	gridFolder:ClearAllChildren()

	for x = 1, size do
		grid[x] = {}

		for y = 1, size do
			grid[x][y] = {}

			for z = 1, size do
				local m = math.noise(x / perlinDivisor, y / perlinDivisor, z / perlinDivisor) < 1/perlinDivisor and 0 or 1

				local part = Instance.new("Part")

				part.Anchored = true
				part.Size = baseVector
				part.Position = Vector3.new(x, y, z) * baseVector
				part.Color = Color3.new(m, 0, 0)
				part.Transparency = 0.8
				part.CastShadow = false
				part.Parent = gridFolder

				grid[x][y][z] = {
					part = part,
					flood = false,
					m = m
				}
			end
		end
		task.wait()
	end

	return grid
end

local function checkForIslands(grid)
	local island = false -- until disproven.
	for x, yzGrid in pairs(grid) do
		for y, zGrid in pairs(yzGrid) do
			for z, val in pairs(zGrid) do
				if val.m == 0 and not val.flood then
					val.part.Transparency = 0
					island = true
				end
			end
		end
		return island
	end
end

local function flood(grid, x, y, z)
	if grid[x][y][z].flood then return end

	grid[x][y][z].flood = true
	grid[x][y][z].part.Color = Color3.new(0, 1, 0)
	task.wait()
	--grid[x][y][z].part.Color = Color3.new(0, 0, 1)
	grid[x][y][z].part.Transparency = 1

	-- X
	if grid[x - 1] and grid[x - 1][y] and grid[x - 1][y][z].m == 0 then
		coroutine.wrap(flood)(grid, x - 1, y, z)
	end
	if grid[x + 1] and grid[x + 1][y] and grid[x + 1][y][z].m == 0 then
		coroutine.wrap(flood)(grid, x + 1, y, z)
	end

	-- Y
	if grid[x] and grid[x][y - 1] and grid[x][y - 1][z].m == 0 then
		coroutine.wrap(flood)(grid, x, y - 1, z)
	end
	if grid[x] and grid[x][y + 1] and grid[x][y + 1][z].m == 0 then
		coroutine.wrap(flood)(grid, x, y + 1, z)
	end

	-- Z
	if grid[x] and grid[x][y] and grid[x][y][z - 1] and grid[x][y][z - 1].m == 0 then
		coroutine.wrap(flood)(grid, x, y, z - 1)
	end
	if grid[x] and grid[x][y] and grid[x][y][z + 1] and grid[x][y][z + 1].m == 0 then
		coroutine.wrap(flood)(grid, x, y, z + 1)
	end

	if x == size and y == size and z == size then
		print("Island?:", checkForIslands(grid))
	end
end

local grid = generateGrid()

task.wait(1)

flood(grid, 1, 1, 1)

Let me know whether this is what you’re looking for or not.
In case yes, let me know if this is enough and you can take on from here or you would like further guidance for your specific case.

1 Like

why are you so smart dude

(this is off topic btw)

1 Like

Quite appreciated, haha!
That script I posted isn’t that advanced: The other member I based-off over provided the actual solution, my 3D implementation is just a simple to-3D extrapolation of it.

1 Like