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
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!
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.
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.
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.
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…
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
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. 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.