workspace:GetBoundingBox() is not capturing all of the terrain in the game

I think I understand what you’re saying. I found some old experiments with pathfinding tutorials where two tables exist, toSearch and searched. You start off by adding one point to toSearch and then run a while loop of the function until toSearch is exhausted. Rather than using strictly recursive function calls, the function simply adds the cell to searched after searching it and removes it from toSearch. If it finds the cell is valid, it adds the neighbors to toSearch.

Unfortunately I tried all of this just now, and it doesn’t seem to have made much of a difference, as now the script times out rather then the stack overflowing.

-- . . .
local toSearch = {}
local searched = {}
local boundsMin, boundsMax = Vector3.one * math.huge, Vector3.one * -math.huge
-- . . .
-- recursive function for flood fill algorithm
local function dfs(pos)
	if table.find(searched, pos) then table.remove(toSearch, table.find(toSearch, pos)) return end

	local min = pos - (Vector3.one * VOXEL_SIZE * 0.5)
	local max = pos + (Vector3.one * VOXEL_SIZE * 0.5)
	
	local materials, occupancies = workspace.Terrain:ReadVoxels(Region3.new(min, max), 4)
	local size = materials.Size

	local bounded = false
	for x = 1, size.X do
		for y = 1, size.Y do
			for z = 1, size.Z do
				local material = materials[x][y][z]
				if (material ~= Enum.Material.Air and material ~= Enum.Material.Water) then
					bounded = true
				end
			end
		end
	end
	table.insert(searched, pos)
	table.remove(toSearch, table.find(toSearch, pos))
	if bounded == true then
		--debugNode(pos, VOXEL_SIZE)
		if min.X < boundsMin.X then boundsMin = Vector3.new(min.X, boundsMin.Y, boundsMin.Z) end
		if min.Y < boundsMin.Y then boundsMin = Vector3.new(boundsMin.X, min.Y, boundsMin.Z) end
		if min.Z < boundsMin.Z then boundsMin = Vector3.new(boundsMin.X, boundsMin.Y, min.Z) end
		if max.X > boundsMax.X then boundsMax = Vector3.new(max.X, boundsMax.Y, boundsMax.Z) end
		if max.Y > boundsMax.Y then boundsMax = Vector3.new(boundsMax.X, max.Y, boundsMax.Z) end
		if max.Z > boundsMax.Z then boundsMax = Vector3.new(boundsMax.X, boundsMax.Y, max.Z) end
		for i = 1, #TERRAIN_CELL_OFFSETS do table.insert(toSearch, (pos + TERRAIN_CELL_OFFSETS[i] * VOXEL_SIZE)) end
	end
end
-- generates an accurate bounding box that includes terrain
local function getTrueBoundingBox()
	table.insert(toSearch, Vector3.zero + Vector3.one * VOXEL_SIZE * 0.5)
	while #toSearch > 0 do dfs(toSearch[1]) end
	-- . . .
end

octree-spacing.rbxl (4.0 MB)

I made a “cooldown” function in my version that yields temporarily after enough time has been spent running code. It’s something like:

local TIMEOUT = 1 / 60 -- you might be able to make this shorter
local latest = os.clock()

local function cooldown()
	if (os.clock() - latest) > TIMEOUT then
		task.wait() -- i did a convoluted thread delay thing before, ignore that
		latest = os.clock()
	end
end

You can either put a call to this in the while loop or dfs itself.

Did you manage to get your version to actually complete scanning? The implementation I used here is starting to make me think I’m working in the wrong direction. I have two blocks of terrain, one that’s 2048 x 512 x 2048 and another that’s 2048 x 1024 x 2048. With a voxel size of 8, that means upwards of +12.5 million potential calculations. I lowered the time all the way down to a cooldown every 10 seconds (which was cutting it close) and there was still no end in sight.

octree-spacing.rbxl (4.0 MB)

If I’m running this once at the beginning of every game, I can’t afford to have the load time be more than 20 seconds absolute tops. That’s also including the actual collision detection scan included in the other topic mentioned in OP, which takes a while, too.

EDIT: I just tested it with a voxel size of 64; the scan does eventually conclude, and the results are correct, but I would still like to try to make the system more efficient because I plan for the maps to be much larger than the example that I’m using (2048 x 4096 x 1024).

Hey, I just got a notification that my post was linked and I think I can be of some help here. If I understand correctly, you’re trying to make a pathfinding algorithm using octrees and you need the terrain’s size to determine the size of those?

I need the terrain’s size so that I can figure out how large the map is (in conjunction with workspace:GetBoundingBox() for base parts) so I can most efficiently space out root nodes (the nodes where the octree starts from) across the map, which would be used to generate a collision map of all of the surfaces, which in turn would be used as a flow-field pathfinding grid for AI. I’m keeping the root nodes a consistent size of 512^3 for now, but the code allows for me to make them bigger or smaller. I’m not trying to use the map’s bounding box to determine the size of the root nodes, if that’s what you’re asking.

Okay, so I see that you’ve already tested with a voxel size of 64 but according to my calculations the biggest number you can call ReadVoxels with is a region of 160^3. I’ve also done some tests afterwards and the lower the number of total calls (regardless of region size), the faster it goes. I’m not very sure how your implementation is, but if you need the number to be a divisor of 512, you can try 128^3.

I tested 128^3 on the largest map I planned to scan (6144 x 512 x 6144), and it takes 15-25 seconds to work. It’s much better than before but I was hoping for faster. Are there any other optimizations that can be made to the script?

Well, that depends on your usecase. Do you just need the XZ size of the map, or do you need the Y dimension too? Also, are your maps more or less squares/rectangles or do they have any weird lines sticking out? In addition to this, is the bottom part more or less flat?

To reduce response times, I’ll write a few of my ideas:
If you have a map with a flat bottom and no weird line stuff, what you can do is this:
Instead of scanning the whole map, just scan in each direction. If you do this, you can reduce 9216 calls to just 100 calls.
If you don’t have a map with a flat bottom and a bunch line stuff, do the same as before, but when you reach an empty space, scan that whole section. If you don’t find anything else, then that’s the last piece of terrain in that direction. If you find other things, continue calling ReadVoxels in the position of that piece of terrain until it ends and repeat. I’m not sure how well this will work, really. Picture to explain:


This might sound a bit hacky, but you could also switch to raycasts (like my original solution). It should be a lot faster.

Anyways, I don’t mean to have you redo everything you’ve done so far, but I’d really really switch to raycasts if I were you.

I don’t know whether this is what you were advising, but I changed the script so that, rather than adding all of the searched node’s neighbors to the queue after processing, it would only add those neighbors that could potentially increase the max region bounds or decrease the minimum region bounds. Using just this qualifier, I managed to cut the number of calls in half from +12k to just about 6k. The process now takes roughly 9 seconds instead of 20. Considering I should be using a depth-first search for this process, I don’t know why I didn’t start with this as part of the script.

It should be mentioned I tried to reduce this further by adding only those with bounds that strictly exceeded the established bounds (min < boundsMin rather than <= and vice versa) but the script wouldn’t properly search all of the cells it needed to to find the accurate bounds of the map. I don’t know what would be causing this, but it’s running out of cells to search long before it should. If I can get the entire search down to five seconds, I’ll be satisfied. Maybe that involves doing a breadth-first search instead?

Hey, I just saw your reply. Sorry for the delay. That wasn’t exactly what I was advising, but I’m glad it worked out.
Personally, I feel like you’re going too complicated. In my opinion, this is a really simple problem. I know I’ve already recommended for you to use my original (after reformatting it of course, it’s horrible otherwise) raycast solution (because of how easy and efficient it is), but I just want to push on that a bit more.
Anyways, if you still want to use ReadVoxels, this is what I meant:


Basically, instead of searching the whole region outside the bounds, just search in a line.

is there a solution to this yet? i was making a terrain serializer earlier today, and i really need to find the terrain real size, because i figured out all of the usual methods are stuck at this mark:
image

Depends on what you consider a solution. I’ve found a method, although it’s a bit hacky, but it works somewhat well and it’s really efficient. NovelDev’s solution takes a bit longer (I believe it was around 20 seconds).
You can check out my idea here:

thank god i have notifications turned on, tysm will check it out…

just tried your method, its not working accurately, i made a random terrain sketch and when i made it bigger, the size decreased.

That’s what I meant when I said it only worked somewhat well. OP’s solution is a lot more accurate but takes a lot more time (20s vs 1s). You should check out some of the other ideas posted on the thread though. There’s a few with which you can greatly reduce the amount of time and make it more accurate (eg. workspace:GetBoundingBox() is not capturing all of the terrain in the game - #21 by Cxsnls)

yeah i get it, considering the terrain size is 32000 and lets say each chunk is 600, 600, 600 and its a huge amount of data being passed, its pretty ineffective, also im not sure if you can agree, but i was making a terrain into string serializer, and its going pretty good except the performance and space it takes to store the data, i was planning to post it in community resources but its just awful, do you think you might help? or not feeling like it.

I think it would help. Personally I’ve made a Terrain Serializer (or something similar) myself until I realized it was not going to finish reading all the cells for 2 hours. I decided to let it go anyways and see what it happened. It gave me a blue screen of death because my computer ran out of memory (I’m very serious). I think it’s a good idea to post it anyway to prevent others from going down the same rabbit hole.

yeah i am actually saving some space by using an open source string compressor, still does not work really good, it takes like 20 minutes to serialize 3000, 1000, 3000 voxels, around ~2600 chunks of 120,000 characters, so yeah i guess i can make it open source, will post a link here for anyone who wants to check.

Also, for reference, for other people who are planning on doing something similar in the future, the more memory you use, the slower it gets. So use less memory to make it A LOT quicker.

For anyone who’s looking for a terrain into string encoder, feel free to look here, its MAX-Optimized to compress huge amounts of data chunks, and read as much terrain as possible, in an interval of 20 mins: