Careful, part.CFrame.RightVector isn’t always guaranteed to be perpendicular to the normal vector. (edit: Was thinking of a corner wedge as one if it’s faces isn’t perpendicular to it’s rightface, sorry). (Though, I think Roblox auto-fixes the CFrame if the local axes don’t have orthogonality anyway.)
Also, it might be better if the part lies flat on the surface with the same x, z position. That code puts half of the part ontop, and half below the surface. If you wish to have the part lie “flat” on any surface at any exact x, z position, I would use a slightly modified version of my code above along with your code.
local part2 = Instance.new('Part', workspace)
part2.Name = "Indicator";
part2.Size = Vector3.new(1,1,1)
part2.BrickColor = BrickColor.Red()
part2.Transparency = 0.5
part2.Anchored = true
part2.CanCollide = false
local ignoreList = {}
table.insert(ignoreList, part2)
function PointLocalVectorAt(objectSpaceVector, originPosition, lookAtPosition)
return CFrame.new(originPosition, lookAtPosition)*CFrame.new(Vector3.new(), objectSpaceVector):inverse()
end
local testDistance = 100
while wait() do
local ray = Ray.new(workspace.Part.Position, Vector3.new(0, -1, 0)*testDistance)
local part, hit, surfaceNormal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
local ySize = part2.Size.y
local x, z = workspace.Part.Position.x, workspace.Part.Position.z --Position.x & Position.z of part being placed on wedge
local posn = hit + surfaceNormal*ySize/2
local x0, y0, z0 = posn.X, posn.Y, posn.Z
local y = -(surfaceNormal.x*(x - x0) + surfaceNormal.z*(z - z0))/surfaceNormal.y + y0
local newPosition = Vector3.new(x, y, z)
part2.CFrame = PointLocalVectorAt(Vector3.new(0, 1, 0), newPosition, newPosition + surfaceNormal)
end
This should work with terrain too I believe, as well as any BasePart.