BasePart:Contains(Vector3) (working code included!)

Introduction:
Many times you want to know if a player is in a certain room, or if a ball is inside the goal etc. Many people do not know linear-algebra (and neither do I, apparently, since I’v failed it 6 times) nor do they really understand CFrames.
Therefore I suggest the function BasePart:Contains!

Code:

_G.PartContains = function(part, pos)
	pos = pos - part.Position
	
	local x,y,z, x1,x2,x3, y1,y2,y3, z1,z2,z3 = part.CFrame:components()
	local xvec = Vector3.new(x1,y1,z1)
	local yvec = Vector3.new(x2,y2,z2)
 	local zvec = Vector3.new(x3,y3,z3)
	local lenx = math.abs(xvec:Dot(pos))
	local leny = math.abs(yvec:Dot(pos))
	local lenz = math.abs(zvec:Dot(pos))
	
	return lenx <= part.Size.x/2 and leny <= part.Size.y/2 and lenz <= part.Size.z/2
end

Demo place: Contains.rbxl (11.9 KB)

I know the code isn’t the slickest thing you’ve ever seen but I thought it was worth giving it a shot since it’s so incredibly useful. I’v wanted a function like this since back in the days of 2009! Let me know what you think.

Please like if you think this would be an awesome addition to BasePart functions!

8 Likes

I’d be down, especially if it properly did wedges, cylidners, and spheres. Can’t go wrong with QoL features.

Also so you know, you can simplify (from a reading standpoint at least) that starting part of that function a lot more with CFrame:pointToOjectSpace(V3). Here’s an example if you’re interested.

2 Likes

Hmm, would be good, yeah.

Here’s a code snippet from one of my scripts, which checks if the Camera’s CFrame is within a model containing parts of shape Block, Cylinder or Ball.

local pt_block = Enum.PartType.Block
local pt_cylinder = Enum.PartType.Cylinder
local pt_ball = Enum.PartType.Ball

local function inGeometry(model)
	local p = cam.CoordinateFrame.p
	for k, v in next, model:GetChildren() do
		if v:IsA("BasePart") then
			local lp = v.CFrame:pointToObjectSpace(p)
			local pt = v.Shape
			local s = v.Size / 2
			if v:FindFirstChild("Mesh") then
				if v.Mesh:IsA("BlockMesh") then
					pt = pt_block
					lp = (v.CFrame * CFrame.new(v.Mesh.Offset)):pointToObjectSpace(p)
					s = v.Mesh.Scale * v.Size / 2
				elseif v.Mesh:IsA("CylinderMesh") then
					pt = pt_cylinder
					lp = (v.CFrame * CFrame.new(v.Mesh.Offset)):pointToObjectSpace(p)
					s = v.Mesh.Scale * v.Size / 2
				elseif v.Mesh:IsA("SpecialMesh") and v.Mesh.MeshType == Enum.MeshType.Sphere then
					pt = pt_ball
					lp = (v.CFrame * CFrame.new(v.Mesh.Offset)):pointToObjectSpace(p)
					s = v.Mesh.Scale * v.Size / 2
				end
			end
			if pt == pt_block and lp.X >= -s.X and lp.X <= s.X and lp.Y >= -s.Y and lp.Y <= s.Y and lp.Z >= -s.Z and lp.Z <= s.Z then
				return true
			elseif pt == pt_cylinder and v2(lp.X, lp.Z).magnitude <= s.X and lp.Y >= -s.Y and lp.Y <= s.Y then
				return true
			elseif pt == pt_ball and lp.magnitude <= s.X then
				return true
			end
		end
	end
	return false
end

But yeah, having this built-in would make for a faster approach.

1 Like

That’s interesting, you essentially implemented a CFrame*Vector3. It might be faster to just use that instead of initializing three Vector3s and calling three dot products from Lua. This is what it’d look like:

local abs = math.abs
function PartContains(part, pos)
    local objectPos = part.CFrame:pointToObjectSpace(pos)
    local partSize = part.Size --avoid three extra indices! it adds up, you know
    
    return abs(objectPos.X) <= partSize.X / 2 and abs(objectPos.Y) <= partSize.Y / 2 and abs(objectPos.Z) <= partSize.Z / 2
end

I didn’t check if this actually worked, but given your implementation I’m sure it could be rectified if necessary. If you do happen to run a little benchmark, let me know, I’m curious.

4 Likes

Well I know my code isn’t the fastest, but it was easy to read.
Since I don’t know what pointToObjectSpace looks like I can only assume that THIS code is >= that code:

function PartContains(part, pos)
pos = pos - part.Position
local x,y,z = pos.x, pos.y, pos.z
local _,_,_, x1,x2,x3, y1,y2,y3, z1,z2,z3 = part.CFrame:components()

return math.abs(x1*x + y1*y + z1*z) < part.Size.x/2 and math.abs(x2*x + y2*y + z2*z) < part.Size.y/2 and math.abs(x3*x + y3*y + z3*z) < part.Size.z/2
end

Now that I think of it, pointToObjectSpace involves an inverse matrix operation. It should still be faster regardless, since indexing a ROBLOX type is a heavier operation than some C++ math. I’m going to benchmark this now…

aww sheet XD

0xBAADF00D = 0.028883218765259 seconds
PlaceRebuilder = 0.094512701034546 seconds

for a 10k benchmark on both of them.

giphy.gif

Fits with my results:

Trial 1 results: 
    Version A took 86.317300796509ms
    Version B took 45.615673065186ms


Trial 2 results: 
    Version A took 92.517375946045ms
    Version B took 39.961099624634ms


Trial 3 results: 
    Version A took 88.299751281738ms
    Version B took 39.918661117554ms


Trial 4 results: 
    Version A took 86.978435516357ms
    Version B took 40.17448425293ms


Trial 5 results: 
    Version A took 87.146282196045ms
    Version B took 39.705514907837ms


Trial 6 results: 
    Version A took 87.375164031982ms
    Version B took 39.72339630127ms


Trial 7 results: 
    Version A took 87.229013442993ms
    Version B took 39.851427078247ms


Trial 8 results: 
    Version A took 87.252855300903ms
    Version B took 40.204286575317ms


Trial 9 results: 
    Version A took 89.881181716919ms
    Version B took 41.054010391235ms


Trial 10 results: 
    Version A took 87.719440460205ms
    Version B took 41.566610336304ms

Version A is the OP code, version B is mine. Fun :stuck_out_tongue:

Also, I tried the second snippet you shared with the dot products moved to Lua; it was a bit faster, averaging ~62ms for a trial of 1,000.

The lesson here is that indexing is slow. :wink: Also, every object you create (Vector3, CFrame, etc) needs to be garbage-collected, so you’ll have even more fun down the line with that.

1 Like

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