How to Get Size From Surface

It’s completely painful to use manmade arbtirary sets as identifiers for surface size. Yes, it “gets the job done”, but I’ve been taking 2 hours to still make the if statements and it’s hurting my brain

P A I N


(If you don’t know what is happening, if I click anywhere other than the line I’m working on it resets my scrollbar)

This only works for cubes.

local Players = game:GetService("Players")

local player = Players.LocalPlayer
local mouse = player:GetMouse()

local ref = {
	[Enum.NormalId.Left] = Vector3.new(1, 1, 0),
	[Enum.NormalId.Right] = Vector3.new(1, 1, 0),
	[Enum.NormalId.Bottom] = Vector3.new(1, 0, 1),
	[Enum.NormalId.Top] = Vector3.new(1, 0, 1),
	[Enum.NormalId.Back] = Vector3.new(0, 1, 1),
	[Enum.NormalId.Front] = Vector3.new(0, 1, 1)
}

while wait(1) do
	local target = mouse.Target
	
	if target then
		local size = mouse.Target.Size * ref[mouse.TargetSurface]
		print(size)
		
		local sX = size.X == 0 and 1 or size.X
		local sY = size.Y == 0 and 1 or size.Y
		local sZ = size.Z == 0 and 1 or size.Z
		
		local surfaceArea = sX * sY * sZ
		print(surfaceArea)
	end
end

If you want to do one for every part then that’s impossible, because MeshParts exist. I’m pretty sure that there’s no current possible way to calculate their surface areas.

Okay… I might have needed to post my build script.

local e = false
local qr
local tb
local mou = game.Players.LocalPlayer:GetMouse()
local function raysurfaceface(e,g)
	local dotY = g.CFrame:VectorToObjectSpace(e):Dot(Vector3.new(0,1,0))
	local dotX = g.CFrame:VectorToObjectSpace(e):Dot(Vector3.new(1,0,0))
	local dotZ = g.CFrame:VectorToObjectSpace(e):Dot(Vector3.new(0,0,1))
	local surface
	if (math.abs(dotY) >= 0.9) then
		if (dotY < 0) then
			surface = Enum.NormalId.Bottom
		else
			surface = Enum.NormalId.Top
		end
	elseif (math.abs(dotX) >= 0.9) then
		if (dotX < 0) then
			surface = Enum.NormalId.Left
		else
			surface = Enum.NormalId.Right
		end
	else
		if (dotZ < 0) then
			surface = Enum.NormalId.Front
		else
			surface = Enum.NormalId.Back
		end
	end
	return surface
end
local function vec3Func(f, ...)
	local x, y, z = {}, {}, {}
	for i, v in next, {...} do
		x[i], y[i], z[i] = v.x, v.y, v.z
	end
	return Vector3.new(f(unpack(x)), f(unpack(y)), f(unpack(z)))
end
local function getRotationBetween(u, v, axis)
	local dot, uxv = u:Dot(v), u:Cross(v)
	if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
	return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end
local function getSurfaceCF(part, lnormal)
	local pcf, size2 = part.CFrame, part.Size/2
	local transition = getRotationBetween(Vector3.new(0,0,1), lnormal, Vector3.new(0,1,0))
	local size = vec3Func(math.abs, transition:VectorToWorldSpace(part.Size))
	return (pcf * transition) * CFrame.new(size/2 * Vector3.new(0, 0, 1)), size
end
local function boundingbox(size,cf)
	local a = {
		Vector3.new(0,0,0)+cf.RightVector.Unit*size.X/2+cf.UpVector.Unit*size.Y/2+cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)+cf.RightVector.Unit*size.X/2+cf.UpVector.Unit*size.Y/2-cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)+cf.RightVector.Unit*size.X/2-cf.UpVector.Unit*size.Y/2+cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)+cf.RightVector.Unit*size.X/2-cf.UpVector.Unit*size.Y/2-cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)-cf.RightVector.Unit*size.X/2+cf.UpVector.Unit*size.Y/2+cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)-cf.RightVector.Unit*size.X/2+cf.UpVector.Unit*size.Y/2-cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)-cf.RightVector.Unit*size.X/2-cf.UpVector.Unit*size.Y/2+cf.LookVector.Unit*size.Z/2,
		Vector3.new(0,0,0)-cf.RightVector.Unit*size.X/2-cf.UpVector.Unit*size.Y/2-cf.LookVector.Unit*size.Z/2
	}
	local minlpx = 0
	local minlpy = 0
	local minlpz = 0
	local maxlpx = 0
	local maxlpy = 0
	local maxlpz = 0
	for i=1,#a do
		if a[i].X<minlpx then
			minlpx=a[i].X
		end
	end
	for i=1,#a do
		if a[i].Y<minlpy then
			minlpy=a[i].Y
		end
	end
	for i=1,#a do
		if a[i].Z<minlpz then
			minlpz=a[i].Z
		end
	end
	for i=1,#a do
		if a[i].X>maxlpx then
			maxlpx=a[i].X
		end
	end
	for i=1,#a do
		if a[i].Y>maxlpy then
			maxlpy=a[i].Y
		end
	end
	for i=1,#a do
		if a[i].Z>maxlpz then
			maxlpz=a[i].Z
		end
	end
	return Vector3.new(minlpx,minlpy,minlpz), Vector3.new(maxlpx,maxlpy,maxlpz)
end



local tb = Instance.new("Part",workspace)
tb.Anchored=true
script.Event.Event:Connect(function(q,r)
	if e==false then
		e = true
		qr = q
	end
end)
game:GetService("RunService").RenderStepped:Connect(function()
	if e==true then
		if qr then
			local rcp = RaycastParams.new()
			rcp.FilterType=Enum.RaycastFilterType.Blacklist
			rcp.FilterDescendantsInstances={game.Players.LocalPlayer.Character,tb}
			local ray = workspace:Raycast(workspace.CurrentCamera.CFrame.Position,(mou.Hit.p-workspace.CurrentCamera.CFrame.Position).Unit*500,rcp)
			if ray==nil then
				tb.Parent=nil
			else
				local f1 = 0
				local f2
				print(ray.Normal.Unit)
				if math.abs(ray.Normal.Unit.X)>f1 then
					f1=math.abs(ray.Normal.Unit.X)
					f2 = "x"
				end
				if math.abs(ray.Normal.Unit.Y)>f1 then
					f1=math.abs(ray.Normal.Unit.Y)
					f2 = "y"
				end
				if math.abs(ray.Normal.Unit.Z)>f1 then
					f1=math.abs(ray.Normal.Unit.Z)
					f2 = "z"
				end
				local r = 45
				local jarsx,jarsy,jarsz
				if f2=="y" then
					jarsx,jarsy,jarsz = ((CFrame.lookAt(Vector3.new(0,0,0),ray.Normal.Unit,ray.Instance.CFrame:VectorToWorldSpace(Vector3.new(0,1,0)))*CFrame.fromOrientation(math.rad(-90),0,0))*CFrame.fromOrientation(0,math.rad(r),0)):ToOrientation()
				else
					jarsx,jarsy,jarsz = ((CFrame.lookAt(Vector3.new(0,0,0),ray.Normal.Unit,Vector3.new(0,1,0))*CFrame.fromOrientation(math.rad(-90),0,0))*CFrame.fromOrientation(0,math.rad(r),0)):ToOrientation()
				end
				tb.Parent=workspace
				local i1 = Vector3.new(ray.Position.X,ray.Position.Y,ray.Position.Z)
				--if raysurfaceface(ray.Instance.CFrame:VectorToObjectSpace(ray.Normal))==Enum.NormalId.Bottom then
				--	i1=i1-(tb.PrimaryPart.CFrame.UpVector.Unit*(tb.PrimaryPart.Size.Y/2))
				--elseif raysurfaceface(ray.Instance.CFrame:VectorToObjectSpace(ray.Normal))==Enum.NormalId.Top then
				--	i1=i1+(tb.PrimaryPart.CFrame.UpVector.Unit*(tb.PrimaryPart.Size.Y/2))
				--elseif raysurfaceface(ray.Instance.CFrame:VectorToObjectSpace(ray.Normal))==Enum.NormalId.Front then
				--	i1=i1+(tb.PrimaryPart.CFrame.LookVector.Unit*(tb.PrimaryPart.Size.Z/2))
				--elseif raysurfaceface(ray.Instance.CFrame:VectorToObjectSpace(ray.Normal))==Enum.NormalId.Back then
				--	i1=i1-(tb.PrimaryPart.CFrame.LookVector.Unit*(tb.PrimaryPart.Size.Z/2))
				--elseif raysurfaceface(ray.Instance.CFrame:VectorToObjectSpace(ray.Normal))==Enum.NormalId.Right then
				--	i1=i1+(tb.PrimaryPart.CFrame.RightVector.Unit*(tb.PrimaryPart.Size.X/2))
				--elseif raysurfaceface(ray.Instance.CFrame:VectorToObjectSpace(ray.Normal))==Enum.NormalId.Left then
				--	i1=i1-(tb.PrimaryPart.CFrame.RightVector.Unit*(tb.PrimaryPart.Size.X/2))
				--end
				local intended = CFrame.new(i1)*CFrame.fromOrientation(jarsx,jarsy,jarsz)
				local p1,p2 = boundingbox(tb.Size,CFrame.fromOrientation(jarsx,jarsy,jarsz))
				local intended2
				print(f2)
				local kars
				if raysurfaceface(ray.Normal.Unit,ray.Instance)==Enum.NormalId.Top or raysurfaceface(ray.Normal.Unit,ray.Instance)==Enum.NormalId.Bottom then
					kars = "y"
				elseif raysurfaceface(ray.Normal.Unit,ray.Instance)==Enum.NormalId.Front or raysurfaceface(ray.Normal.Unit,ray.Instance)==Enum.NormalId.Back then
					kars = "z"
				elseif raysurfaceface(ray.Normal.Unit,ray.Instance)==Enum.NormalId.Right or raysurfaceface(ray.Normal.Unit,ray.Instance)==Enum.NormalId.Left then
					kars = "x"
				end
				if f2=="y" then
					if kars=="y" then
						print('ya')
						intended2 = CFrame.new(Vector3.new(math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).X,-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y,math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Z,-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)))
					elseif kars=="z" then
						print('za')
						print(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y)
						intended2 = CFrame.new(Vector3.new(math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).X,-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y,-ray.Instance.Size.Y/2+math.abs(p1.Z-p2.Z)/2,ray.Instance.Size.Y/2-math.abs(p1.Z-p2.Z)/2),ray.Instance.CFrame:PointToObjectSpace(intended.Position).Z))
					elseif kars=="x" then
						print('xa')
						print(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y)
						intended2 = CFrame.new(Vector3.new(ray.Instance.CFrame:PointToObjectSpace(intended.Position).X,math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y,-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Z,-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,ray.Instance.Size.Y/2-math.abs(p1.Z-p2.Z)/2)))
					end
				elseif f2=="x" then
					intended2 = CFrame.new(Vector3.new(ray.Instance.CFrame:PointToObjectSpace(intended.Position).X,math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y,-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Z,-ray.Instance.Size.Z/2+math.abs(p1.Z-p2.Z)/2,ray.Instance.Size.Z/2-math.abs(p1.Z-p2.Z)/2)))
				elseif f2=="z" then
					intended2 = CFrame.new(Vector3.new(math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).X,-ray.Instance.Size.X/2+math.abs(p1.X-p2.X)/2,ray.Instance.Size.X/2-math.abs(p1.X-p2.X)/2),math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y,-ray.Instance.Size.Y/2+math.abs(p1.Y-p2.Y)/2,ray.Instance.Size.Y/2-math.abs(p1.Y-p2.Y)/2),ray.Instance.CFrame:PointToObjectSpace(intended.Position).Z))
				end
				intended2=CFrame.new(ray.Instance.CFrame:PointToWorldSpace(intended2.Position))
				intended2=CFrame.new(intended2.Position)*CFrame.fromOrientation(jarsx,jarsy,jarsz)
				game:GetService("TweenService"):Create(tb,TweenInfo.new(.1/3,Enum.EasingStyle.Quad,Enum.EasingDirection.InOut),{CFrame=intended2}):Play()
			end
		end
	end
end)

As you can see, it’s not as simple as finding the just the measure of the area. I need to find the size vector of the surface so I can perform clamping. As you can see the code is super messy and not done yet.

Now I want you to do these things.

  1. Place script in your game
  2. Make five parts
  3. First part is sized (50,1,20) rotated (0,0,0)
  4. Second part is sized (1,50,20) rotated (0,0,90)
  5. Third part is sized (1,50,20) rotated (0,45,90)
  6. Fourth part is sized (50,20,1) rotated (85,12,2)
  7. Fifth part is sized and rotated whatever you want

If you can figure out how to make the building part clamped to the bounds of the surface of the part that the raycast goes to, then you have figured out the solution to my problem.

Assuming that you have the normal of the ray hit, and that the part is a block…

local function getSurfaceArea(part, surfaceNormal)
    -- Get the normal relative to the part's orientation.
    -- e.g. this would return (0, -1, 0) when you hit the bottom of the part, no matter how the part is oriented in the world.
    local vObj = part.CFrame:VectorToObjectSpace(surfaceNormal).Unit
    -- Get the absolute value of each component in the Vector, and then subtract that to (1, 1, 1) to get
    -- the factor we'll multiply to the part's Size.
    -- e.g. a vObj of (0, -1, 0) will translate to a factor of (1, 0, 1)
    local factor = Vector3.new(1, 1, 1) - Vector3.new(math.abs(vObj.X), math.abs(vObj.Y), math.abs(vObj.Z))
    -- Multiply this to the part's Size. Tada.
    -- Note that a:Dot(b) == a.X * b.X + a.Y * b.Y + a.Z * b.Z
    local surfaceArea = factor:Dot(part.Size)
    return surfaceArea
end
3 Likes

Someone told me on discord to change the name of the post from Size to Area.
It seems you and the other guy misinterpreted what I wanted. I don’t want the number of the area of the size. I want the vector size.

Fair, that only requires a small alteration to the script I posted.

local function getSurfaceSize(part, surfaceNormal)
    local vObj = part.CFrame:VectorToObjectSpace(surfaceNormal).Unit
    local factor = Vector3.new(1, 1, 1) - Vector3.new(math.abs(vObj.X), math.abs(vObj.Y), math.abs(vObj.Z))
    local surfaceSize = part.Size * factor
    return surfaceSize
end

Assuming the size of the part is (2, 4, 8):

  • It will return (0, 4, 8) if it hits the left or right
  • It will return (2, 0, 8) if it hits the top or bottom
  • It will return (2, 4, 0) if it hits the front or back

Ahh, Vector2. That’s tough, but it may be possible to resort to if-statements to convert each case to a Vector2. Does it even have to be a Vector2? Unless you’re working with UI I don’t think you’ll find it that useful here.

2 Likes

Confused. I’m not sure what you’re trying to do with all this information. A building system? My script gave the vector size at the beginning, called “size.” You could make the numbers in the ref table negative if you need opposite sides.

Thanks for the professionalism. I have a part that is sized (50,40,0) and is rotated (90,0,0). When I’m hovering over the top side, with normal (0,1,0), I expect the function to return (50,1,40). Instead, what it returns, is still (50,40,0).

In other words, it would be as if to lay one part on top of the face of the other, rotated (0,0,0), and sized (50,40,1), it would encompass the entire surface of the latter part which I am hovering over, yet having the rotation of (0,0,0), whereas the latter part would have the rotation of (90,0,0)

The part in red would be the part which I have given the example that it would encompass the surface my mouse would be hovering on. It has the size (50,1,40) and rotation (0,0,0) and is positioned 1 stud above the part with the decal on it.
The decal on the part with the decal on it is facing its front side, and its size is (50,40,1), and is rotated (90,0,0).



In another visual analogy, I want to find the hypothetical size of this red part with its decal facing top. which in this case, would be (50, 0.001, 40)

local function Side(CRay)
	local List = {"UpVector","LookVector","RightVector"} -- Define Different Vector Directions
	local List2 = {} -- Create List For Faces
	local Sides = {"Top","Bottom","Front","Back","Right","Left"} -- Define Returned Value -- Change This To Modify Output
	for count = 1,6,1 do
		local modi = -1+(count%2)*2 -- Check if Vector Should Be Flipped
		List2[CRay.Instance.CFrame[List[math.ceil(count/2)]]*modi] = Sides[count] -- Apply Vector To List
	end
	return List2[CRay.Normal] -- Return Hit Face Of Part
end
print(Side(--[[ Put Ray Here ]]))

I just did this

@magicalmariomario,
I tried out your script (admittedly I modified it so that it worked with raycasts)
and it doesn’t work. One example is that for the top facing surface which is the front surface of a part sized (50,40,1) and rotated (90,0,0), it returned (1,40,2), which is not what I expected, and what I expected was (50,1,40).

@dudesa3000, I have said this twice already, I don’t want to find the side name. I already know how to do that and have a function for it. I want to find the size of the side,

With the side name you can get the size all you have to do is change out the side names with vector3s and then when it returns the side multiply it by the size of the hit object

local function Side(CRay)
	local List = {"UpVector","LookVector","RightVector"} -- [[Define Different Vector Directions]]
	local List2 = {} -- [[Create List For Faces]]
	local Sides = {{"X","Y","Z"},{"X","Z","Y"},{"Y","X","Z"}} -- [[Define Returned Value ]]-- [[Change This To Modify Output]]
	for count = 1,6,1 do
		local modi = -1+(count%2)*2 -- [[Check if Vector Should Be Flipped]]
		List2[CRay.Instance.CFrame[List[math.ceil(count/2)]]*modi] = Sides[math.ceil(count/2)] -- [[Apply Vector To List]]
	end
	local Size = CRay.Instance.Size
	local N = List2[CRay.Normal]
	return Vector3.new(Size[N[1]],Size[N[2]],Size[N[3]]) -- [[Return Hit Face Size Of Part]]
end
print(Side(--[[ Put Ray Here ]]))

That’s not what I was looking for though, for example, the top facing face of this part,

rotated (90,0,0) and sized (85,45,1) should return (85,1,45), but using your function, would return (1,1,0), and when I multiply it with the size of that part, it would give (85,45,0), which is not what I expected.
@XAXA, any ideas here?

I am incredibly dumb. So sorry I’ll fix it

It should work now, just added a new function.

1 Like

I’ll take a crack at this in a minute. Seems fun

Thanks, but I’ve encountered another problem.

print(math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).X,-getSurfaceSize(ray).X/2+math.abs(p1.X-p2.X)/2,getSurfaceSize(ray).X/2-math.abs(p1.X-p2.X)/2))
print(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Y)
print(math.clamp(ray.Instance.CFrame:PointToObjectSpace(intended.Position).Z,-getSurfaceSize(ray).Z/2+math.abs(p1.Z-p2.Z)/2,getSurfaceSize(ray).Z/2-math.abs(p1.Z-p2.Z)/2))

This is what I’m printing.
image
Now instead what I expect is
image

The position of the raycast to the local CFrame of the raycast instance does not return what I wanted, so I thought of the solution to make it :PointToObjectSpace() to a CFrame which is,

CFrame.lookAt(ray.Instance.Position,ray.Normal.Unit,UPVECTOR)

Now the “UPVECTOR” is what I’m trying to find. I need the upvector in order for the lookAt to work, because if a part looks like this,


and I only give it the normal vector, the CFrame might look like this

Because an Upvector hasn’t been specified,
But you can’t just find the UpVector of the raycast instance’s CFrame, because as I said, multiple parts with different rotations and sizes could look just like that big flat part in the image.
If the upvector has been specified however, the CFrame will look like this

Which is what I’m trying to solve.

The Part.CFrame.UpVector is the Normal

So whenever you perform a raycast, the only vector you’re given is the normal vector.


But I want to find the upvector of that normal vector.

Now I can’t just do “ray.Instance.CFrame.UpVector”, because sometimes the upvector of the instance could be below or to the right of the ray’s normal vector.
So what I’m trying to find is that blue axis which I have circled.

Now the part’s UpVector is ambiguous.
It could be here


or here

or here

Depending on the part’s rotation