Introducing OverlapParams - New Spatial Query API

Oh my god this is soo goos I love it. It will really change a lot, and help me out :slight_smile:

Thanks guys

1 Like

Will these new region3 functions have any performance benefits over the old ones?

2 Likes

I noticed a spelling error in “Workspace” you had put “Worksapce”

2 Likes

It’s only baseparts, so no. Zone plus is still still great tool!

3 Likes

Yess! Now I can hopefully continue working on my guitar hero Roblox remake!

2 Likes

Will we be getting a boxcasting and spherecasting implementation next? Or will line-of-sight checks fall to developers?

5 Likes

I swear everytime I am in the middle of developing a system using my own method you guys release a beautiful alternative and I have to go in and change what I made the same day.

Brilliant! Thank you for this!

4 Likes

In my game, monsters are placed in a specific directory. If the returned result can be limited to the children in the specified directory with type of “Model”, that will be more better. I will not need to traverse which part is that monster.
When there is a limit on the number, it would be even better if we can choose to filter by distance

3 Likes

this is cool but still please dont remove findpartsinregion3 as a lot of scripters use it. (ofc they can update it) but some games have very lively communities while still not receiving updates.

2 Likes

This is deprecation, not removal. It probably won’t be removed for a long time due to the amount of code that relies on it.

4 Likes

Deprecation doesn’t mean removal.

Deprecation is usually a standardized way to point developers at the newer better function that superseded an older one, not an actual indication that the older function will be removed at some point. Heavily used APIs like this will likely never be removed despite being deprecated.

14 Likes

I may be using it incorrectly but from my tests :GetTouchingParts() performed significantly better than using :GetPartBoundsInBox() despite creating a new CheckArea part at the start of the FindSpace() function.

For context, I have tree models in ServerStorage which I then use their BoundingBox to get both the Size for TouchingParts and BoundingBox for PartBoundsInBox methods, then find a space via Raycast and set the area to check based on the area found to locate other trees in the area.

Source Code

GetTouchingParts (30-40 FPS, 4 second render time)

local CheckPart = nil
	if typeof(ModelSize) == "Vector3" and #GrassVectors > 0 then
		CheckPart = ServerStore.CheckArea:Clone()
		CheckPart.Size = ModelSize
		CheckPart.Touched:Connect(function(HitPart) end)
		CheckPart.Parent = Work
	end
	
	local Attempts = 0
		repeat
			Attempts = Attempts + 1
			local GrassData = GrassVectors[math.random(1, #GrassVectors)]
			local Result = game.Workspace:Raycast(Vector3.new(math.random(GrassData.Min.X, GrassData.Max.X), GrassData.Min.Y + 1000, math.random(GrassData.Min.Z, GrassData.Max.Z)), Vector3.new(0, -2000, 0), Configs.GrassParams)
			if Result.Instance == GrassData.Part then
				if CheckPart then
					CheckPart.CFrame = CFrame.new(Result.Position + Vector3.new(0, CheckPart.Size.Y / 2, 0))
					local Parts = CheckPart:GetTouchingParts()
					for Index, Object in pairs(Parts) do
						if Object:FindFirstAncestor("Trees") and Object:FindFirstAncestor("Trees") == Configs.TreeStorage then
							Parts = false
							break
						end
					end
					if Parts == false then
						continue
					end
				end
				if CheckPart then
					CheckPart:Destroy()
				end
				return Result.Position + Vector3.new(0, 0.5, 0)
			end
		until Attempts >= Configs.FindSpaceAttempts

PartBoundsInBox (2.5 FPS, 29 second render time)

local Attempts = 0
		repeat
			Attempts = Attempts + 1
			local GrassData = GrassVectors[math.random(1, #GrassVectors)]
			local Result = game.Workspace:Raycast(Vector3.new(math.random(GrassData.Min.X, GrassData.Max.X), GrassData.Min.Y + 1000, math.random(GrassData.Min.Z, GrassData.Max.Z)), Vector3.new(0, -2000, 0), Configs.GrassParams)
			if Result.Instance == GrassData.Part then
				--[Temp] Tree Models are not oriented correctly, X and Y axis are reversed
				local TreeParts = workspace:GetPartBoundsInBox((ModelFrame - ModelFrame.Position) + Vector3.new(Result.Position.X, Result.Position.Y + (ModelSize.X / 2), Result.Position.Z), ModelSize, Configs.TreeFilter)
				if #TreeParts > 0 then
					continue
				end
				return Result.Position + Vector3.new(0, 0.5, 0)
			end
		until Attempts >= Configs.FindSpaceAttempts
		return false

GetPartsInPart (2.5 FPS, 31 second render time)

local CheckPart = nil
	if typeof(ModelSize) == "Vector3" and #GrassVectors > 0 then
		CheckPart = ServerStore.CheckArea:Clone()
		CheckPart.Size = ModelSize
		CheckPart.Touched:Connect(function(HitPart) end)
		CheckPart.Parent = Work
	end
	if #GrassVectors > 0 then
		local Attempts = 0
		repeat
			Attempts = Attempts + 1
			local GrassData = GrassVectors[math.random(1, #GrassVectors)]
			local Result = game.Workspace:Raycast(Vector3.new(math.random(GrassData.Min.X, GrassData.Max.X), GrassData.Min.Y + 1000, math.random(GrassData.Min.Z, GrassData.Max.Z)), Vector3.new(0, -2000, 0), Configs.GrassParams)
			if Result.Instance == GrassData.Part then
				if CheckPart then
					CheckPart.CFrame = CFrame.new(Result.Position + Vector3.new(0, CheckPart.Size.Y / 2, 0))
					local Parts = game.Workspace:GetPartsInPart(CheckPart, Configs.TreeFilter)
					if #Parts > 0 then
						continue
					end
				end
				if CheckPart then
					CheckPart:Destroy()
				end
				return Result.Position + Vector3.new(0, 0.5, 0)
			end
		until Attempts >= Configs.FindSpaceAttempts
		if CheckPart then
			CheckPart:Destroy()
		end
		return false
	end

Edit: Did another quick test and :GetPartsInPart() had the same issue when attempting to convert :GetTouchingParts() to it.

2 Likes

After being initially excited, I am a bit confused and slightly disappointed with this new library for the following reasons:


Performance differences of new APIs
The performance of these new functions, while may be improved over alternative methods (like external libraries which support rotated region detection and native solutions such as GetTouchingParts which have problems such as collision requirements) are very poor compared to a simple AABB test through API like FindPartsInRegion3. Here’s an example of this poor performance when we use GetPartsInPart:

GetPartsInPart vs FindPartsInRegion3 results

The first number indicates the amount of parts, second number is the amount of milliseconds for 100 API calls, third element is if GetPartsInPart is used (true) or FindPartsInRegion3 is used (false):

0 0.0778ms false
0 0.4018ms true

25 0.0913ms false
25 6.1577ms true

100 0.0825ms false
100 21.1336ms true

1000 0.0768ms false
1000 240.5298ms true

5000 0.0632ms false
5000 1459.7305ms true

10000 0.0665ms false
10000 3090.1008ms true

Source code for benchmark: GitHub Gist

Now this is nearly unusable for games that have large amounts of parts inside the spatial query (30.9ms for a call with 10,000 parts), so I switched to try GetPartBoundsInBox which was better with these results:

GetPartBoundsInBox vs FindPartsInRegion3 results

Similar format results to before, where the first number indicates the amount of parts, second is the amount of milliseconds for 100 API calls, third element is if GetPartBoundsInBox is used (true) or FindPartsInRegion3 is used (false):

0 0.1103ms false
0 0.4088ms true

25 0.0964ms false
25 4.1149ms true

100 0.0760ms false
100 5.3696ms true

1000 0.0620ms false
1000 49.1931ms true

5000 0.0646ms false
5000 528.7402ms true

10000 0.0846ms false
10000 1249.3728ms true

Source code for benchmark: GitHub Gist

While this is certainly improved, an average API call of 12.49ms is nearly a full frame! This is a very expensive operation but how often do we really need to query 10,000 parts which are all inside where we are looking? Not that often, so I decided to try out the MaxParts and filtering features to see if this changes the speed.

As a developer, I have use-cases where only a simple AABB test is necessary rather than one which utilizes rotated regions. The speed implications of having to do rotational checks makes it infeasible for some of my use cases, so I have to resort to using deprecated functions like FindPartsInRegion3.

I think it’s important to note here that the intended use case of FindPartInParts is where your BasePart has custom geometry, like a MeshPart — which is harder to calculate, so this probably explains why FindPartInParts is so slow. It’s also important to note that in all these benchmarks all the parts are inside our query, so it is much slower. I explain how a spread out map makes this much more efficient at the end of this post in the interesting benchmarks section.


MaxParts is not fully effective

In my tests, I found this to be partially true, and the API still consumes a large amount of time. This is only a minor problem, as FilterDescendantInstances solves most of the use case of checking if specific parts are inside a box, but it is still important to note that calls are still expensive even with MaxParts set to 1 and increase proportionally to the amount of parts.

GetPartBoundsInBox with MaxParts vs FindPartsInRegion3 results

Similar format results to before, where the first number indicates the amount of parts, second is the amount of milliseconds for 100 API calls, third element is if GetPartBoundsInBox is used alongside MaxParts set to 1 (true) or FindPartsInRegion3 is used (false):

0 0.1057ms false
0 0.9415ms true

25 0.1370ms false
25 1.1478ms true

100 0.0670ms false
100 2.0912ms true

1000 0.0869ms false
1000 19.3874ms true

5000 0.0662ms false
5000 103.2925ms true

10000 0.2380ms false
10000 213.2466ms true

Source code for benchmark: GitHub Gist

So we can see that while MaxParts does work to some extent, API calls are quite slow. This is particularly concerning because it should have only had to iterate over one part, as all our parts are made inside our hitbox the first part it looks up should fulfill the maximum parts it needs to look for — so I was not expecting the time to still scale linearly.


FilterDescendantsInstances causes worse performance
In my tests, I found that using FilterDescendantsInstances actually causes worse performance than if you were to not use it, even if the instance whitelist was only a small subsection of parts under WorldRoot. I thought this was very bizarre, because naturally I would expect this to not scale linearly since I have set the descendant whitelist to be the first 25 parts, but similar to MaxParts, it seems to be scaling again, which is very counter-intuitive. Here’s the benchmark results:

GetPartBoundsInBox with 25 FilterDescendantsInstances results

Similar format results to before, where the first number indicates the amount of parts, second is the amount of milliseconds for 100 API calls, third element is if GetPartBoundsInBox is used alongside FilterDescendantsInstances set to 25 parts (true) or FindPartsInRegion3 is used (false):

0 0.0708ms false
0 0.3183ms true

25 0.0760ms false
25 2.4035ms true

100 0.0624ms false
100 7.2634ms true

1000 0.1041ms false
1000 81.3375ms true

5000 0.0742ms false
5000 765.0460ms true

10000 0.0678ms false
10000 1717.8362ms true

Source code used for this benchmark: GitHub Gist

As we can see from the benchmark results, this is actually slower than just not using FilterDescendantsInstances at all (see GetPartBoundsInBox vs FindPartsInRegion3 results).
I think this is very important to know about, because you would be better off not using the FilterDescendantsInstances and instead iterating over results and checking if they are what you are looking for yourself in many cases. These footguns with the new API make it very concerning for me to use.


Concluding thoughts

While these new APIs seem very easy to use and simple, they have huge performance implications which make them unusable in some cases where a large amount of parts are inside the area being checked. My biggest problem is that since the old methods have been marked as deprecated, what am I supposed to use for a simple AABB test when I do not need rotational checking and I need high performance code?

Overall, this is exciting! But there are some things I am confused about how this new API works which would be great to have some clarification.


Sidenote, some small interesting benchmarks
Some users have asked about whether using GetPartBoundsInRadius is faster than GetPartBoundsInBox — I found that GetPartBoundsInBox is marginally better more often, but I do not think that the difference is significant and you should go with whatever your use case is.

GetPartBoundsInRadius vs GetPartBoundsInBox

I ran these on a Hitbox with size 50, 50, 50
Similar format results to before, where the first number indicates the amount of parts second is the amount of milliseconds for 100 API calls third element is if GetPartBoundsInRadius is used (true) or GetPartBoundsInBox is used (false):

0 0.5943ms false
0 0.8691ms true

25 2.1426ms false
25 2.3293ms true

100 5.0867ms false
100 4.4336ms true

1000 47.9035ms false
1000 34.5973ms true

5000 606.7480ms false
5000 637.8543ms true

10000 1367.7854ms false
10000 997.0864ms true

Source code for this benchmark: GitHub Gist

The size of the box influences the amount of time it takes when using GetPartsInPart — I found that there was at least some level of correlation between the size of the part and the amount of time that it took. I did not extensively test this however, so feel free to do so yourself.

This new API uses some really cool spatial understanding to filter out what it needs to check. In all these benchmarks I’ve shared, all the parts have been inside the query. When I spread the parts out a bit, so that roughly ~1/4 are inside the query, the function performs 30x faster! This is really good news, so definitely benchmark these new APIs in your game to see if they perform faster than custom rotated solutions (sidenote: It is still much slower than FindPartsInRegion3, I had 0.11ms vs 48ms when I spread the parts out so that 1/4 of the parts were inside my query). I cannot stress how cool this is, because it means most use cases of these new APIs will have perfectly fine performance if you need a rotated check.

If you want to run any of the benchmarks I have posted throughout this thread, my Hitbox was made with the following parameters:

local Workspace = game:GetService("Workspace")
local ChangeHistoryService = game:GetService("ChangeHistoryService")

ChangeHistoryService:SetWaypoint("BeforeHitbox")

local Hitbox = Instance.new("Part")
Hitbox.Name = "Hitbox"
Hitbox.Size = Vector3.new(42.1, 25.1, 54.3)
Hitbox.CFrame = CFrame.new(-94.5620041, 23.2386665, -55.8205566, 0.492402792, -0.413175881, -0.766040146, 0.642785311, 0.766043305, -1.1920929e-07, 0.586822629, -0.492403567, 0.642783582)
Hitbox.Transparency = 0.5
Hitbox.Anchored = true
Hitbox.CanCollide = false
Hitbox.Parent = Workspace

ChangeHistoryService:SetWaypoint("AfterHitbox")
47 Likes

I often use magnitude in for loops to get parts in a radius. will these new additions not only be a better alternative but also better performance-wise?

3 Likes

As a combat-games developer, this is probably the best thing that I could ever ask for. Thanks a lot for this Roblox.

3 Likes

Anyone know how to convert FindPartsInRegion3 to WorldRoot:GetPartBoundsInBox. :sob:

Semi-joke aside, is there a performance difference between FindPartsInRegion3 versus GetPartBoundsInBox? I run a very small region check every heartbeat for my anticheat and I’m curious if it’ll improve speeds/decrease possible script activity in Script Performance.


Edit:

I’m extremely pleased with this update. Originally I had some not-so-performant code when running a Region3 check every heartbeat and with this new library I was able to easily condense it. Because of this new library I cut down on a nice chunk of script activity.

Thanks for another nice update. :slightly_smiling_face:

2 Likes

I’m so excited. Another great update :slight_smile: .

1 Like

It’s almost sad how much easier it is now, due to all of the lines I made to do these things manually… Althoug I guess that’s the point!

this is a unrealistic benchmark – what game would ever have 10k or more parts in 1 part?
plus your stats could be faked

You really just don’t want to be nice for once, but that type if thing is called a BENCHMARK, theres a very low chance you would need that many parts in one part, but it is used for BENCHMARKING performance.

6 Likes