Rotating a part's local Y axis based from raycast's normal offset

Howdy Robloxians,

I’m not an expert at CFrame math and I’ve had a tough time trying to figure out how I can rotate a part around its own local Y axis as it is offset from a raycast’s hit normal.

The main issue that I need to fix is this part of the code:

VisualPart.CFrame *= CFrame.Angles(0, math.rad(10), 0)

Although this works, it only works for when the part is on a flat surface. As seen in the videos below it makes the part rotate around its Y axis, but as the part’s orientation changes due to the raycast normal offset, it begin as to rotate around its Y axis, but in its own World Space which makes the part rotate in a wonky way which is not wanted.

The video below shows what the part looks like without any rotational transformations, the part offsets its own orientation to match the raycast’s normal. What I want the part to do for it to rotate also around its Y axis by a set increment (it being as of now math.rad(10)).

The video below shows how the rotation on the part is constant, but as its offset from the raycast changes due to the terrain’s elevation, the rotation of the part itself does not rotate along its own Y axis, but rotates all of the part’s axes by the world space CFrame of the part

The video below shows how when I turn the camera, the part follows along with it, but its rotation remains unwanted

If you are wondering, yes I have read through hundreds of other forum post’s solutions, which none have helped to fix this.

Feel free to give your feedback or suggestions on how I could fix this, thank you!

Source Code:
RotationPart.lua (1.6 KB)

Source code without downloading the .lua file
-- Local script in player
local Players = game:GetService("Players")
local Player = Players.LocalPlayer

local Character = Player.Character or Player.CharacterAdded:Wait()

local Camera = workspace.CurrentCamera

local RayLength = 50

-- Makes the visual part you can see
local function rayVisual(color)
	local part = Instance.new("Part")
	part.Anchored = true
	part.CanCollide = false
	part.CanTouch = false
	part.CanQuery = false
	part.Color = color
	part.Transparency = .5
	part.Size = Vector3.new(2,5,2)
	part.Name = "raypart"
	part.Parent = workspace
	
	return part
end

local VisualPart = rayVisual(Color3.fromRGB(255,0,0))

wait(1)

game:GetService("RunService").Stepped:Connect(function()
	local LookVec = Camera.CFrame.RightVector:Cross(-Player.Character.HumanoidRootPart.CFrame.UpVector)

	local Origin = (Player.Character.HumanoidRootPart.CFrame + (LookVec * RayLength/3)).Position + Vector3.new(0,RayLength/1.5,0)
	
	local Direction = -Player.Character.HumanoidRootPart.CFrame.UpVector * RayLength

	local newRay = workspace:Raycast(Origin, Direction)
	
	if newRay then
-- Positions the part based off the normal in which the ray hits
		VisualPart.CFrame = CFrame.new(newRay.Position, newRay.Position + newRay.Normal:Cross(VisualPart.CFrame.RightVector))

-- Main part for of the code which I am trying to rotate the part based off its own local y axis
		VisualPart.CFrame *= CFrame.Angles(0, math.rad(10), 0)
		
	end	
end)
1 Like

I see you are using the CFrame.new method,

The issue is that it generates its own right vector which you tried to amend with cross product.

I believe this method is more reliable which you havent seemed to mention:

1 Like

I have the angle of the surface set, and the part just needs the part to rotate around its local Y axis. I’ll try your method nonetheless to see if it works :)!

It somewhat works!
It does make the part turn around its axis, but it jitters and glitches randomly

here is the code I changed:

local randomAxis = Vector3.new(1,0,0)

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

game:GetService("RunService").Stepped:Connect(function()
	local LookVec = Camera.CFrame.RightVector:Cross(-Player.Character.HumanoidRootPart.CFrame.UpVector)
	
	local Origin = (Player.Character.HumanoidRootPart.CFrame + (LookVec * RayLength/3)).Position + Vector3.new(0,RayLength/1.5,0)
	
	local Direction = -Player.Character.HumanoidRootPart.CFrame.UpVector * RayLength

	local newRay = workspace:Raycast(Origin, Direction)
	
	if newRay then
		local rotateToFloorCFrame = getRotationBetween(VisualPart.CFrame.UpVector, newRay.Normal, randomAxis)
		VisualPart.CFrame *= rotateToFloorCFrame * CFrame.Angles(0, math.rad(10), 0)
		VisualPart.Position = newRay.Position
		
		--VisualPart.CFrame = CFrame.new(newRay.Position, newRay.Position + newRay.Normal:Cross(VisualPart.CFrame.RightVector))
	end	
end)

Try doing this and keep in mind the CFrame order of operations is extremely important and determines whether the changes are applied in WorldSpace or ObjectSpace.

		VisualPart.CFrame = rotateToFloorCFrame * VisualPart.CFrame * CFrame.Angles(0, math.rad(10), 0)

2 Likes

Works perfectly now!
Thank you so much I had no idea that about the order of operations decided the WorldSpace or ObjectSpace!

As a side question, would you happen to know how I could make the part face the player such as the “CFrame.LookAt” function using the same method above? Instead of using “math.rad(10)” increment in the CFrame.Angles portion it would be using instead the direction of the players root!

1 Like

No worries learnt about the order of operations the hard way with months of CFrame practice.

If you want it to face the player you should just be able to CFrame.lookAt as normal without the rotation to surface.

You just need to provide the third parameter which is the upvector.

CFrame.lookAt(part.Position, player.Position, surfaceNormal)

That method does work, but the thing about it is that it wont also orient the part from the raycast normal which leads to this behavior:

I would say that the function shouldn’t actually use the “CFrame.LookAt” function but instead change the unit from the “CFrame.Angles” portion

VisualPart.CFrame = rotateToFloorCFrame * VisualPart.CFrame * CFrame.Angles(--insert direction facing player here--)
1 Like

Good work testing this.

I think another method you could try is to measure the angle between the parts look vector and the direction from part to humanoid root part, then apply that as the Y axis in CFrame.Angles.

1 Like

Implemented a version of your method and it works!

Can’t thank you enough, you saved my brain from imploding! Best wishes!

1 Like