Convert LookVector/Surface Normal to rotation


I would like to know how to convert a LookVector / a Surface Normal to rotation.

For example, a normal of (0, 1, 0) would equal to (0, 0, 90).

Thanks !

Do you mean converting degrees to a rotation value? (Like math.rad)

If you are just interested in the z axis rotations then it’d be

(math.cos(angle), math.sin(angle), 0) <- (0,0,math.deg(angle))

(x, y, 0) -> (0,0,math.deg(math.atan2(y, x)))

Edit: angles are in radians so math.deg added

Yeah, I need to convert all axises so this won’t work.

If you want a full description of a rotation you need more than one normal as an input. Also I imagine it’d be easier to just use the cframes rather than orientation vectors

I’m doing a raycast so the only thing that I have is a normal. I’m trying to remake a simple version of the scan test demo which would be compatible with my phone vr module.

I need to convert normal to rotation to rotate the triangle the right way.

Not sure what scan test demo is or what triangles you’re using. The normal should correspond to the up/right/look vector of the CFrame you need

A lookvector doesn’t contain enough data to describe rotation and I’m not sure where you’re getting the 90 Z rotation from

1 Like

Ok, so what i’m essentially trying to do is like sticking a paper on a surface. In my case, I need the normal to determine which direction my paper will be facing.

Yeah look vector is not enough information

But from my knowledge there are only two methods to make up for this lack of information


For more info this uses cross product with the direction vector and UpVector to generate the rest of the axises.

This is good if you don’t care about the rotation along that surface normal axis let’s say for a circular/cylindrical object like a blood puddle.


Getting the shortest rotation from one vector to another vector. This allows for rotation along that surface normal axis which is good for systems like a character controller.

1 Like

Just before you replied, I tried that but it didn’t work which is weird.

From what I’ve read you would want to set the paper’s CFrame with CFrame.fromMatrix
Something along the lines of

local forwardVector = RaycastResult.Normal
local upVector =, 1, 0)
local rightVector = forwardVector:Cross(upVector)
local upVector2 = rightVector:Cross(forwardVector)

Paper.CFrame = CFrame.fromMatrix(RaycastResult.Position,rightVector,upVector2)

To prevent the paper from clipping into the wall it is placed on you may want to add the Normal times the thickness of the paper to the position, like this
Paper.CFrame = CFrame.fromMatrix((RaycastResult.Position+RaycastResult.Normal*Paper.Size.X),rightVector,upVector2)

Isn’t there column missing Vz? I’m asking it here as I personally don’t understand anything in rotation matrixes.

When left blank “the third column is calculated as [vx:Cross(vy).Unit]” from the documentation. Pretty sure its more efficient to let Roblox do it internally but I could be totally wrong

Tough, I tried it and it yielded the same result as using CFrame.lookAt ( which just looks to have some axis inverted ) but it sometimes didn’t work at all ( setting the Y to 9999… ) and created weird artifcact where the part would disseapear randomly.

As I said, the only problem that I can see with the LookAt is that some axises are inverted.

What do you mean by axes are inverted exactly? It seems like it may be fixed by multiplying by a constant CFrame. E.g. CFrame.LookAt(…) * CFrame.Angles(math.rad(90),0,0)


Could I see a snippet of your code? or maybe a more detailed explanation of what exactly you are trying to do, because I’m having no issues with what I have sent.

This is what I mean, It is changing with the normal but never the right direction.

Yeah that looks correct, just rotate it by a fixed amount afterwards. Lookvector is Z axis, but those papers don’t appear to be Z forward.

1 Like

So, I managed to fix the issue by changing CFrame.Angles(math.rad(90), 0, 0) to CFrame.Angles(0, math.rad(90), 0).

In the final, it looks like this
Part.CFrame = CFrame.lookAt(Position, Position + Normal) * CFrame.Angles(0, math.rad(90), 0).
The variables name are modified for simplicity.