Trying to reproduce SDS's trowel pre-building effect, a bit lost from here

I am trying to remake SDS’ trowel mechanic for my own game that allows to pre-visualise what your building would where your mouse aims.

(hope the video works)
The first footage is what SDS does, and the second is what I have done so far.
I have no idea however of what I should do next. Where do I start so the block can be properly oriented to be fully parallel to the part/wedge its touching?

1 Like

You could try using CFrame.fromAxisAngle(). It gives a cframe that is rotated around a vector. In this situation, that vector should be perpendicular to both the default upvector and the surface normal. Cross product can be used for that. The amount of rotation (angle) should be the angle between the default upvector and the surface normal. Because they are both unit vectors, their dot product is the cosine of the angle between them.

local upVec =, 1, 0)

local function getCf(surfacePos, normal, partSize)
    local axis = normal:Cross(upVec)
    local angle = math.acos(normal:dot(upVec))
    local pos = surfacePos + normal * (partSize.Y * .5)
    return CFrame.fromAxisAngle(axis, angle) + pos
1 Like

what’s a normal already? i’m not very familiar with that

It’s a vector perpendicular to the surface. There’s a 90 degree angle between the surface and the vector. You can get the surface normal with a raycast.

1 Like

ok so I did the thing but it only works on wedges (and it’s inverted but that’s probably because i screwed up somewhere) and when touching normal parallellepipedic parts the block gets sent to the stratosphere.
My code is as follows:

local detection=workspace:Raycast(script.Parent.TargetBlock.Position,script.Parent.TargetBlock.Position-mouse.Target.Position*-100, raycastParams)
	if detection then

Also, after targeting a part, wedges don’t work anymore too, but that’s not surprising given that my ray is 100 studs long.

(also i replaced :dot by :Dot because the former didn’t work)

If the part rotates in the opposite direction than it should, then I had put the normal and upvector in incorrect order in the cross product, and they need to be swapped.

The way you get the direction and origin for the raycast also seems incorrect. Try using the function updatePartCf when you want to position the part (probably every frame).

local UserInputService = game:GetService("UserInputService")

local RAYCAST_DIST = 100

local camera = workspace.CurrentCamera

local part = script.Parent:FindFirstChild("TargetBlock")

local upVec =, 1, 0)

local function getMouseRayResult()
    local rayParams =
    rayParams.FilterType = Enum.RaycastFilterType.BlackList
    rayParams.FilterDescendantsInstances = {part}
    local mousePos = UserInputService:GetMouseLocation()
    local unitRay = camera:ViewportPointToRay(mousePos.X, mousePos.Z)
    local rayResult = workspace:Raycast(unitRay.Origin, unitRay.Direction * RAYCAST_DIST, rayParams)
    return rayResult

local function getCf(surfacePos, normal, partSize)
    local pos = surfacePos + normal * (partSize.Y * .5)
    local dot = upVec:Dot(normal)
    if 1 - dot < 1e-5 then
    elseif 1 + dot < 1e-5 then
        return CFrame.fromMatrix(pos,, 0, 0),, -1, 0),, 0, -1))
    local axis = upVec:Cross(normal)
    local angle = math.acos(dot)
    return CFrame.fromAxisAngle(axis, angle) + pos

local function updatePartCf()
    local mouseRayResult = getMouseRayResult()
    -- make sure that the ray actually hit something
    if mouseRayResult then
        part.CFrame = getCf(mouseRayResult.Position, mouseRayResult.Normal, part.Size)
1 Like

Ok, it works all good now except for perfectly flat surfaces (roofs included) and the part gets teleported to coordinates 0, -a lot, 0 when that happens

I have now edited the code. Does it now work?

1 Like

It does! thank you very much!

blah blah character limit