Calculating CFrame from position and normal vector

Hello!
I’m working on a placement system. Models should move to the mouse position in 3D space. They should be parallel to the surface of the mouse. Some specific models should be fixed to the floor (not walls).

My issue is calculating the CFrame from a position and normal vector as well as the model’s extends to not clip through walls.

This is my current (client-sided) function to place objects which returns the CFrame. It’s raycasting every frame from the camera to the mouse and working with the returned RaycastResult.

function position(sample:Model)
	local model:Model = sample:Clone()
	model.Parent = workspace
	
	for _,basePart in ipairs(model:GetDescendants()) do
		if basePart:IsA("BasePart") then
			basePart.CollisionGroup = "uncollidable"
		elseif basePart:IsA("ProximityPrompt") then
			basePart:Destroy()
		end
	end

	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	raycastParams.FilterDescendantsInstances = {model,localPlayer.Character}
	local position:Vector3
	local lookAt:Vector3
	local rotation = 0
	local cframe:CFrame

	local connection = runService.RenderStepped:Connect(function()
		local ray = workspace.Camera:ScreenPointToRay(mouse.X,mouse.Y)
		local raycastResult = workspace:Raycast(ray.Origin,ray.Direction*256,raycastParams)
		local static = model.Name == "outfit"
		
		if raycastResult then
			local origin = raycastResult.Position
			local lookAt = static and Vector3.FromNormalId(Enum.NormalId.Bottom) or raycastResult.Normal
			cframe = CFrame.new(origin-lookAt*rotateVectorTowards(model:GetExtentsSize()/2,lookAt),origin)*(static and CFrame.Angles(math.pi/2,rotation,0) or CFrame.Angles(0,math.pi,rotation))
			model:PivotTo(cframe)
		end
	end)

	mouse.Button1Up:Wait()

	connection:Disconnect()
	model:Destroy()
	
	return cframe
end

Placing non-fixed models works in the positive direction of every axis
image
, however not in the negative.
image

Models fixed to the floor clip through it. (“static” boolean)

I’m unsure whether I just messed up somewhere and don’t find it or if my approach is simply wrong.
It’d be great if someone knows more!

local Transformations = setmetatable({}, {
	__index = function(self: {[Enum.NormalId]: Vector3}, index: any)
		local success, result = pcall(function()
			return Vector3.FromNormalId(index)
		end)
		if ((success) and (typeof(result) == "Vector3")) then
			return result
		end
		return Vector3.zero
	end,
}) :: {[Enum.NormalId]: Vector3}
local function GetOffsetRotationFromNormal(normal: Vector3, face: Enum.NormalId?)
	if (typeof(normal) == "Vector3") then
		local Rotation = Transformations[face] * (math.pi/2)
		return CFrame.lookAt(Vector3.zero, normal) * CFrame.fromOrientation(Rotation.Y, Rotation.X, Rotation.Z)
	end
	return CFrame.new()
end

Usage:

GetOffsetRotationFromNormal (
	normal = RaycastResult.Normal or Vector3.Unit: "The normal vector to retrieve the base rotation",
	face = Enum.NormalId: "The face to orient the base rotation"
) -> CFrame Offset Rotation

It will take some fiddling to get the right Enum.NormalId to orient the model correctly, so try everything and you’ll eventually get it!