Maths: Why is the Y component of a Cross Product a suitable replacement for angular values in CFrame.Angles()?

I have been looking at CFrame animation scripts for making the player face the mouse position, and I have seen it done similarly in this way every time:

Waist.C0 = Waist.C0:lerp(WaistOriginC0 * CFrame.Angles(0, (((HeadPosition - MousePosition).Unit):Cross(TorsoLookVector)).Y, 0), 0.25)

What is confusing me is that the argument in the Y component of CFrame.Angles( ) does not SEEM to be a measurement of angular displacement. From my understanding of this component:

  • (HeadPosition - MousePosition).Unit is the Unit Vector MH pointing from Mouse.Hit.Position to the players Head, and TorsoLookVector is merely the Unit Vector the torso is pointing, as seen in the image below:

  • When we take the cross product of MH and the TorsoLookVector we get a cross product represented in blue (or the new third line) in the image below

  • The Y directional component of this unit vector is then used to rotate the Neck/Waist Motor6D C0 component in a CFrame.Angles() multiplication. This is where I am confused and would greatly appreciate an explanation as to why they are not using an angle for this component.


Because it was made by cleversource and people just use it as is.

The main source of confusion, also going to tag someone else confused with it. @nghiapoopie123

No need for understanding or maths. It’s just cross product property and the fact it will generate a vector that is perpendicular to the two vectors inputted.

What clever source I believe has done is think oh I know cross product has a direction component so it can tell the difference between looking right and left vs dot product which doesn’t.

So you use the two inputs which I believe you already know torso look vector, and mousehit to head position. Here is the results:

When the mouse is right the cross product will be pointing downwards, hence the Y component is - which indicates clockwise movement looking at the character from a birds eye view from the Y axis.

When the mouse is left the cross product will be pointing upwards, hence the Y component is - which indicates anti clockwise movement looking at the character from a birds eye view from the Y axis.

And that’s pretty much it then it’s been multiplied by 0.5 in order to make sure the torso doesn’t rotate too far, and yeah.

For this part

It actually kinda is when you consider that the magnitude of the cross product of two unit vectors

The formula to convert two unit vectors cross product into an angle is

local unit1 = (HeadPosition - Point).Unit
local unit2 = TorsoLookVector
local vector = ((unit1 ):Cross(unit2 ))
print(math.asin(vector.Magnitude)*180/math.pi) --get the angle in degrees
--Assuming X,Z component is zero or close to it like the above two images then
--vector.Magnitude ≈ vector.Y -- close enough lol

However this gets rid of the directionality component of the unit vector

Here is the visualized version of the code feel free to play around with it:

Vector visualization
local RunService = game:GetService("RunService")

local Player = game.Players.LocalPlayer
local PlayerMouse = Player:GetMouse()

local Camera = workspace.CurrentCamera

local Character = Player.Character or Player.CharacterAdded:Wait()
local Head = Character:WaitForChild("Head")
local Neck = Head:WaitForChild("Neck")

local Torso = Character:WaitForChild("UpperTorso")
local Waist = Torso:WaitForChild("Waist")

local Humanoid = Character:WaitForChild("Humanoid")
local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")

for i,v in pairs(Character:GetChildren()) do
	if v:IsA("BasePart") then
		v.Transparency = 0.8

local NeckOriginC0 = Neck.C0
local WaistOriginC0 = Waist.C0

--Neck.MaxVelocity = 1/3
local function Visualize(v3, origin, color, thickness, transparency)
	local origin = origin or
	local part ="Part")
	part.CanCollide = false
	part.Name = "Vector"
	part.BrickColor = color or BrickColor.Random() 
	part.Transparency = transparency or 0
	part.Anchored = true
	part.Size = or 0.2, thickness or 0.2, v3.magnitude)
	part.CFrame =, origin+v3)
	part.Locked = true
	part.Parent = workspace
	return part

	local CameraCFrame = Camera.CoordinateFrame

	if Character:FindFirstChild("UpperTorso") and Character:FindFirstChild("Head") then
		local TorsoLookVector = Torso.CFrame.lookVector
		local HeadPosition = Head.CFrame.p

		if Neck and Waist then
			if Camera.CameraSubject:IsDescendantOf(Character) or Camera.CameraSubject:IsDescendantOf(Player) then
				local Point = PlayerMouse.Hit.p

				local Distance = (Head.CFrame.p - Point).magnitude
				local Difference = Head.CFrame.Y - Point.Y
				local vector = (((HeadPosition - Point).Unit):Cross(TorsoLookVector))
				local part = Visualize(vector*3,HeadPosition,BrickColor.Red())
				local mouse = Visualize(HeadPosition - Point,Point, BrickColor.Green())
				local lookVector = Visualize(TorsoLookVector*10,HeadPosition, BrickColor.Blue())

				--print(math.asin(vector.Magnitude)*180/math.pi) -- to convert it into a angle
				local yAngle = (((HeadPosition - Point).Unit):Cross(TorsoLookVector)).Y * 0.5
				Neck.C0 = Neck.C0:lerp(NeckOriginC0 * CFrame.Angles(-(math.atan(Difference / Distance) * 0.5), (((HeadPosition - Point).Unit):Cross(TorsoLookVector)).Y * 1, 0), 0.5 / 2)
				Waist.C0 = Waist.C0:lerp(WaistOriginC0 * CFrame.Angles(-(math.atan(Difference / Distance) * 0.5), yAngle, 0), 0.5 / 2)

i think i understanded what you mean, but can you pls visualize the y axis too?

1 Like

The y axis for the CFrame angle rotation is just the right hand rule

Positive is anti clockwise, negative is clockwise:


no, i mean the Y axis of the cross product. Pls :slight_smile:

1 Like

And there is one more thing, you said: It actually kinda is when you consider that the magnitude of the cross product of two unit vectors . Well, can you explain more, i did search on google about unit vector on unit circle and as far as i can tell, it still doesn’t connect .

1 Like

It’s from the formula:

Rearranging it and applying the magnitude operator on both sides of the equation we can find the angle:


In this case with a = torsolookvector and b = anotherUnitVector you can simplify it to this as @ThanksRoBama pointed out in your post.

However the creator thought as an approximation that:

|a × b| ≈ (a × b).Y --magnitude is approximately close enough to the y component

And as we can see from the images I sent above it’s basically close enuff as X and Z are close to zero when the mouse is in those positions.

If you want another method which measures the angle between the vectors more accurately I suggest using the angleBetweenSigned function made by @sleitnick


Wait, from what i understand, i thought CFrame.Angles only take the radian angle, not the sin or cos of the radian angle, that wouldn’t work, right?

Nope, not necessarily there is no math.asin used in the original code by clever source yet it still works.

It’s all just approximation, It’s specifically known as small angle approximation.


The values generated by result are sufficient to make the torso rotate.

It just works.

1 Like

ok so the answer to my question is that it just works. Ok, that is an interesting answer but what about:
|a × b| ≈ (a × b).Y --magnitude is approximately close enough to the y component
as well as we can see from the images I sent above it’s basically close enuff as X and Z are close to zero when the mouse is in those positions.. I think i would need more explanations than just that. I’m not sure if i understanded what you mean

Do you know how to calculate magnitude of |a × b|?
you take the xyz components and do this

|a × b| = (x,y,z)
magnitude of a x b = sqrt (x^2 + y^2 + z ^2)
--now ignore x, z, it'll make the actual value lower but its an approximation
--Who cares if the angle is lower then it should be, we don't want the torso to look directly at the mouse anyways
--x = 0, z = 0 like the red cross product vector in the image above
magnitude of a x b= sqrt(0^2 +y^2 +0^2)
magnitude of a x b = sqrt(y^2)
|a x b|  ≈ y --notice the squiggly line, that means approximately as it's not exactly the answer

yeah, but i can’t seem to understand why would X and Z = 0 because when i visualize it on the paper, it seems that if X = 0 then Z would equal to some random number. Anyway, it might be because it is 12 am in my country and i get a little bit tired, i mean headache.

1 Like

You can test it out yourself in studio with his script by printing the values. Within the first 60 degree arc infront of the player (30 degree rotation to each side) arcsin(Crossproduct.Y) is very very similar to Crossproduct.Y.
However, as the player starts turning a larger angle, such as 45 degrees, we can see that the approximation is worse. However, this approximation is merely an UNDER-estimated value of 45 degrees, eg:
Where 44.1 degree rotation is actually approximated to only 39.9 degrees.

One can assume that @CleverSource intended this for a more realistic animation.

Nevertheless, you can add in math.asin for your TRUE mathematically accurate result.


Thanks bro, i appreciate your explanation