How to Get Size From Surface

Hey man, I’m volunteering my time like everyone else on this forum.

Anyways, yes.

This version of the function returns a cframe where the RightVector and -LookVector are parallel to the surface, and the UpVector is pointing out from the surface. The second returned value is the size of the surface in a Vector2 this time for clarity.

To be clear: the CFrame this returns has a positive X-axis which points “to the right”, a positive Z-axis which points “down”, and a positive Y-axis which points “out of the page”.

However, the size returned is a Vector2, which means that it’s X-axis is aligned with the CFrame’s X-axis, but the size-X-axis is aligned with the cframe-Z-axis.

Visualization of the CFrame:

-- Returns a CFrame and a Size (Vector2) of the given surface.
--
-- The CFrame:
--   * is positioned at the center of the surface
--   * has an UpVector = the surface normal
--   * has a LookVector aligned with a part edge, and pointing generally upwards in world space.
local function GetWorldOrientedSurface(part: BasePart, normalId: Enum.NormalId): (CFrame, Vector2)
	local cf = part.CFrame
	local rot = cf - cf.Position
	
	local nObject = Vector3.fromNormalId(normalId)
	local nWorld = rot * nObject
	
	-- get orthogonal vector by utilizing the order of NormalId enums
	-- i.e. Front.Value is 5 -> (5+1)%6 = 0 -> Right.Value
	local xWorld = rot * Vector3.fromNormalId((normalId.Value + 1) % 6)
	
	-- get other orthogonal vector
	local zWorld = nWorld:Cross(xWorld)
	
	-- make them both point "generally down"
	if xWorld.Y > 0 then xWorld = -xWorld end
	if zWorld.Y > 0 then zWorld = -zWorld end
	
	-- choose the one pointing "more down" one as the z axis for the surface
	if xWorld.Y < zWorld.Y then zWorld = xWorld end
	
	-- redefine x axis based on that
	xWorld = nWorld:Cross(zWorld)
	
	local surfaceRot = CFrame.fromMatrix(Vector3.new(), xWorld, nWorld, zWorld)

	-- get width of part in direction of x and y
	local sizeInWorldSpace = rot * part.Size
	local sizeInSurfaceSpace = surfaceRot:Inverse() * sizeInWorldSpace
	
	-- get position on surface
	local surfaceCFrame = surfaceRot + cf.Position + nWorld * math.abs(sizeInSurfaceSpace.Y) / 2

	return surfaceCFrame, Vector2.new(math.abs(sizeInSurfaceSpace.X), math.abs(sizeInSurfaceSpace.Z))
end

-- USAGE

local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local p = Instance.new("Part")
p.CanCollide = false
p.Anchored = true
p.Transparency = 0.5
p.BrickColor = BrickColor.Red()
local decal = Instance.new("Decal")
decal.Texture = "http://www.roblox.com/asset/?id=67616959" -- some random "this side up" image
decal.Face = Enum.NormalId.Top -- always the top face
decal.Parent = p
p.Parent = player.Character or player.CharacterAdded:Wait()

game:GetService("RunService").Stepped:Connect(function()
	local part = mouse.Target
	if part then
		local cframe, size = GetWorldOrientedSurface(part, mouse.TargetSurface)
		p.Size = Vector3.new(size.X, 0.1, size.Y)
		p.CFrame = cframe
	end
end)
7 Likes