Rotated Region 3 Module

Looks extremely useful and well made, as per your usual!

I’m unable to check the source code right now, but I’m curious about something.
If you create a sphere region using a part, does it bother checking rotation? Since rotation wouldn’t affect it, does the algorithm skip it and assume orientation of 0,0,0?

5 Likes

Yes and no…

The collision detection is done with the GJK algorithm. In essence it requires a function that takes an aribitary direction as an argument and spits out the furthest point on that shape in that direction.

So in the case of a ball whether it’s rotated or not changes basically nothing as far as GJK is concerned.

That said, technically you can input an arbitrary size into the ball constructor and that would give you an ellipsoid where rotation would matter.

7 Likes

It’s slower than the original GJK module as well as the GetTouchingParts module.

RotatedRegion3.rbxl (33.2 KB)

Probably should mention I ran this on the new VM, and my specs are a 7600k and a 1080ti.

Here’s a more raw benchmark, which gave me the results below.

-- Speedtest.lua

local GJK = require(workspace.RotatedRegion3GJK:Clone())
local GetTouchingParts = require(workspace.RotatedRegion3Touching:Clone())
local GJKV1 = require(workspace.RotatedRegion3GJK1:Clone())

local Functions = {
	function() -- GJK
		local Region = GJK.FromPart(workspace.Region)
		return Region:Cast(workspace.Region)
	end;

	function() -- GetTouchingParts
		local Region = GetTouchingParts.FromPart(workspace.Region)
		return Region:Cast(workspace.Region)
	end;

	function() -- GJKV1
		local Region = GJKV1.FromPart(workspace.Region)
		return Region:Cast(workspace.Region)
	end;
}
local Times = {}
local tick = tick
for a = 1, #Functions do
	local Function = Functions[a]
	local StartTime = tick()
	for a = 1, 1e4 do
		Function()
	end
	local Time = tick() - StartTime
	print(Time)
	Times[a] = Time
end

local ShortestTime = math.min(table.unpack(Times))
local ShortestFunc
for a = 1, #Times do
	if Times[a] == ShortestTime then
		ShortestFunc = a
	end
end
print("Shortest Function:", ShortestFunc)
print("Shortest Time is", ("%.2f%%"):format(100 - (100 * (ShortestTime / math.max(unpack(Times))))), "faster than the longest time")
2.0143620967865
1.2489848136902
1.560450553894
Shortest Function: 2
Shortest Time is 38.00% faster than the longest time
2 Likes

So running the exact same test I got totally different results?

Capture

This does seem more accurate though. GetTouchingParts should be immensely more performant compared to GJK.

In addition, although V1 and V2 share some differences in structure which is prob what leads to V2 being slower (I still recommend it b/c V1 freezes sometimes) they do use almost exactly the same code so the gap in your image seems very unlikely.

6 Likes

This is great! I loved :heart: your old version of RotatedRegion3 and it’s been incredibly useful in many projects. Any time that I want to do collision detection I reach for this tool!

My favorite use case was a C&C Renegade-esque RTS/FPS project I worked on a while back for another person. Players could place buildings like in any other RTS, but they were big enough that players could walk into them! I needed to disallow placing buildings such that they overlap other buildings or the terrain (made of parts), so some kind of collision detection was needed.

I wanted to make it obvious to the player where they could or could not place any given building, so I decided to have a “ghost” version of a building showing where it will be placed, and to color the “ghost” red or green to show if it’s overlapping something:

image image

This means running the collision detection every frame, between several parts. To make this performant with many buildings and terrain consisting of 1000s of parts I used octrees (or quadtrees, don’t remember) to filter out things that were too far away to collide anyway. I also enclosed every building in a single part describing it’s bounding box (not axis aligned), to further filter out buildings that weren’t even close to touching, despite being physically close. Finally, the actual collision detection only needed to be run between 1 to 5 parts per building in the vicinity (5 for the most complicated buildings).

The tower in the pictures has a pretty big overhang, and I wanted to allow players to place small buildings beneath towers, as well as on top of or inside certain other buildings. Instead of just enclosing each building in a single volume that excludes other buildings, the “collision volume” can consist of any number of parts for fairly fine control.

Using these optimizations, we got the game to run with no drop in frame rate on my laptop (Intel HD Graphics 4000 and i5-3320M CPU @ 2.60 GHz). IIRC we didn’t do any actual benchmarks other than “yep, it runs at 60 fps”. For reference, my laptop can’t run Phantom Forces without occasionally dipping below 30 fps.

Anyway, I just wrote this to show an example of how this module can be super useful, and to show that it’s fine to do a few collision checks every frame. In a different game I’m just doing a super simple check to see if the player’s feet are touching the ground, so it’s also useful in simpler cases.

9 Likes

Hey, the old Region module supported calculating intersection points. Is there any way to get those out of this module? I had a look over the source code (the module itself, and GJK too) and I don’t see any of that functionality built-in. Could you consider adding something like the old intersectionPoints method?

6 Likes

I want to know if characters are in a region.
Would this be the method? It’s a dynamic shape of rooms and anglular hallways.

It would mean

Running this every frame for characters.
60FPS, 15 boxes rotated and 30 wedges , how many parts, thus Players (scanning for their HRP) could I manage?

that’s a horrible idea, I suggest you find a better alternative. I’m guessing you’re doing this on the server. Just check every wait() if their HRP position is within the locations you mentioned.

local function checkBounds(hrp,boundSize,boundPos)
	local hrpPos = hrp.Position
	local x,y,z = hrpPos.X,hrpPos.Y,hrpPos.Z
	return (x < boundPos.X + (boundSize.X/2)) and (x > boundPos.X - (boundSize.X/2)) and (y < boundPos.Y + (boundSize.Y/2)) and (y > boundPos.Y - (boundSize.Y/2)) and (z < boundPos.Z + (boundSize.Z/2)) and (z > boundPos.Z - (boundSize.Z/2))	
end

@EgoMoose

Hey, I was super excited to try your module as I just encountered an issue with my collision detection system, and I’ve pinpointed this to be due to the fact that when my models rotate 90 degrees, the Region3 I’m making is still centered in the model, and doesn’t alter it’s bounding box with the model’s actual rotation.

However, it appears I got an internal error with the module (assuming I set it up correctly.)

Any ideas? :frowning:

Right you are!

A month ago or so I pushed a change removing something I thought I didn’t need. Turns out it was necessary and I now remeber why (had to do with the different shapes)!

I’ve gone and fixed that up. Sorry for the difficulty!

3 Likes

woudlent it be easyer to just have some points with magnitude and simple logic
so like say, has to be within magnitude of this point and this point, and not in this point.

I don’t know if I’m setting this up incorrectly, but I just tried again and I got the same error at line 88, attempt to index a nil value. :confused:

Did you update the module?

Line 88 doesn’t have the same code that it used to.

2 Likes

This is actually bizarre, even after deleting and retaking the model in your OP, the line of code at 88 still looks the same, indexing set.

:confused:

It is bizzare. Line 88 has nothing to do with indexing set anymore. It’s a completely different function. I reinserted from the OP again just to make sure:

RobloxStudioBeta_2020-05-01_21-27-28

Line 88 used to be where line 101 is currently. I’m just as confused as you are. Can you open the module and confirm the existence of the get corners function?

1 Like

Bingo. Turns out I needed to restart Studio or open up a new place for it to update marketplace assets apparently.

May or may not be an engine bug, but it’s the updated version now.

2 Likes

Yeah, I’ve been having that same problem of updating a model, inserting it again, and studio inserting the old version. Studio will only insert the new version in a new studio window.

3 Likes

How do i will remove region? pls tell

One way is to create your own :Destroy() Function for the custom object like in the wiki:

However, since I believe the rotated region3 object has no events that you can just set the table to nil like so and wait for garbage collection:

local testRegion = RotatedRegion3.FromPart(sphere)
testRegion = nil

Please correct me if I’m wrong.

@EgoMoose

Hi, I was attempting to use your module for one of my moves, but I was struggling to get it to work properly. Here is the code

local RoadRollerHitbox = REGION3_HITBOX.Block(roadrollermod:GetModelCFrame() ,roadrollermod:GetModelSize())

local partsInRegion = workspace:FindPartsInRegion3(RoadRollerHitbox,50)
		for i , v in pairs(partsInRegion) do
			print("debugprint")
			if v.Parent  and v.Parent:FindFirstChildOfClass("Humanoid") and v.Parent:FindFirstChildOfClass("Humanoid").Health > 0 and (v.Parent:FindFirstChild("Torso")) then
				if #AlreadyDamaged > 0 and table.find(AlreadyDamaged, v.Parent) then print("Prevents it from damaging twice")
					
				else
					
				   local hum = v.Parent:FindFirstChildOfClass("Humanoid")
					table.insert(AlreadyDamaged, hum.Parent)
					
					damageevent:FireServer(hum, chr["Right Arm"].CFrame, 3, 0.4, rot.CFrame.lookVector * 0, "rbxassetid://241837157", 0.5, Color3.fromRGB(255, 0, 0), "rbxassetid://542443306", math.random(9, 11) / 10, math.random(9, 11) / 35, game.Players.LocalPlayer.PlayerGui.Blood.BloodVal.Value) --Handles damage.
					knockevent:FireServer(hum) --Stuns player.
					--end
				end
			end
		end

I don’t work with Region 3 often so I believe I messed something up :sweat_smile:.
This error comes up when I try to use the code above: “Unable to cast Dictionary to Region3”. All help is appreciated! :smile: