Advanced CFrame Math Assistance

Apologies if the math for this isn’t as advanced as the title implies, I have been thinking about this problem for a week and feel no closer to the solution than I was previously. Any insight or assistance is appreciated.

To start, here is a basic rundown of my situation: I am programming a custom first person camera, this camera has multiple functions, but the primary one (the function I’m getting stumped by) is the ability for it to function at an angle. By “an angle”, I mean if the character were to fall on their back, or if an animation were to play, the camera would face the according direction instead of always facing forward with a roll value of 0.

I’ve programmed the basic camera system, 360 degree yaw rotation, pitch with limits, the issue is that I’m not sure how to add the CFrame of the torso to the offset of the camera. I’ve made some progress on the issue, I know that part of the problem is resulting from multiple of the same value being added together; for example; You add together the X axis of where your camera system thinks it is, but then you also add the x axis rotation of the torso, meaning that there is some duplication of the values.

With all that said, here are relevant portions of my code:

local function Mouse_Moved (delta:Vector2)
	local effectiveDelta = Vector2.new(Config.camera.xSens * UserSettings.MouseSensitivity * -delta.X, Config.camera.ySens * UserSettings.MouseSensitivity * -delta.Y)
	
	local newPitch --[[X]] = math.clamp(viewportVector.Y + effectiveDelta.Y, Config.viewport.minPitch, Config.viewport.maxPitch)
	local newYaw --[[Y]] = (viewportVector.X + effectiveDelta.X)
	
	viewportVector = Vector2.new(newYaw, newPitch)
	
	local yawDiff = (torsoYaw - newYaw)
	
	if math.abs(yawDiff) > Config.head.maxYaw then
		torsoYaw = torsoYaw + ( (newYaw - torsoYaw) + (Config.head.maxYaw * (math.abs(yawDiff)/yawDiff) ) )
	end
end
local function Update_View () -- Runs every frame
	local head:Part = assignedCharacter.Head
	local torso:Part = assignedCharacter.Torso
	local displayHead:Part = assignedCharacter.DisplayHead
	local headCF = head.CFrame
	
	if math.abs(forwardPressed) == 1 or alignTorsoWithHead == true then
		torsoYaw = math.lerp(torsoYaw,viewportVector.X,Config.movement.torsoLerpAlpha)
	end
	
	torso.CFrame = CFrame.new(torso.Position) * torso.CFrame.Rotation:Lerp(CFrame.fromEulerAnglesYXZ(math.rad(torso.Orientation.X), math.rad(torsoYaw), math.rad(torso.Orientation.Z)), Config.camera.lerpAlpha) --This is where the rotation would need to be added.
	assignedCamera.CFrame = assignedEyes.WorldCFrame
end

Quite honestly I dont expect anyone to have the time to reply to this question, but I thought I would send it here anyways, just in case anyone can help.

I’m a bit confused as to what you are asking, can you draw a diagram?

Let’s say we start with our humanoid facing forward:

The code works as intended, and moving your mouse will do several things; up and down will simply pitch your camera up or down, up to an assigned limit to avoid 360 pitch rotation. Secondly, it rotates the players head, if it reaches an amount larger than I specified (30 degrees) the humanoid root part (and real head, not display head will start to follow behind.


This above view is an exact reflection of the values stored within the script shared in the original post: viewportVector & torsoYaw

And this is where the tricky parts in; I need to also account for the rotation of the camera in the world space, so if the player falls backwards or gets shoved to the side with an animation like in the image below, the camera will reflect this, and wont always be anchored facing forward and having zero yaw.


I know that part of the problem I’m experiencing is that since if I simply multiplied the global orientation of the humanoid by the variables within the script, there would be double values in some areas (ie: I have viewportVector.X = 20, but that value is also going to be represented when I get the angle of the characters head in world space, so I’ll actually have 40, which is wrong.

There’s essentially one line of CFrame math in between me and getting over this issue (commented in the original post), it’s just such a complex problem that I’m completely stumped on how to get around it.

How do you want the system to behave when the character is lying on its back? That would be helpful for solving the problem. And would help us to understand how your camera system works exactly (from what I understand, the camera rotates the torso and the head, and that is what stumps you, when the character is at an angle?)

1 Like

I feel I am still not understanding what you want.

Are you just wanting the torso’s roll to be added onto the camera’s roll? So that for example, if the character is knocked onto their back (rotated 90 degrees), the camera also rotates 90 degrees the same way?

1 Like

When the character is on its back, I want the cameras orientation to be offset by the orientations distance from 0,0,0. This would be such that if the player fell on their back, they would face the sky, and if they rotated their head right it would move towards looking at the baseplate, as opposed to rotating around the X axis, and looking around on the baseplate.

In essence, what I want is a controllable 360/first person ragdoll camera.

If this doesn’t make sense let me know and i can try to elaborate further.

Yes, precisely, except I’m having trouble figuring out how I can integrate this with the way I currently have my variables and math set up.

In typical roblox characters, the HumanoidRootPart is what determines how torsos are positioned and oriented in the world, which allows for torsos to be animated while allowing the character to be oriented in a given direction: You can pivot the root part to some CFrame, and the torso will follow, and also have animation transforms applied.

The way your code reads to me seems like you use the torso as a root part, which if you’re assigning a new CFrame every frame, will override any animation transforms and prevents the character from being rotated by level geometry (except for the Z axis from what I can see)

I might have it wrong but some clarification here would allow me to help further

1 Like

If I change the torso variable to equal the HumanoidRootPart instead, the code still functions the same, although it’s good to know what you just shared. Once I figure all of this out, the intention is for the “DisplayHead” to have its positioning rely on the real heads position, and sort of just add on any movements by the actual head to combine into the correct orientation. Same deal for the torso, (or humanoidrootpart, as i’ve now changed it to.)

I think the heart of the issue boils down to this:

I’m not sure how to add the orientation of the humanoidRootPart to the camera orientation whilst also excluding the portion of the camera orientation which is already represented by the base viewportVector.

Why not just get the angle between the torso’s lookVector and a base vector (the torso’s lookVector without the y component) and add this on to your camera’s cframe?

1 Like

Could you elaborate on what you mean by this, please?

This is a basic demonstration of what I mean, I changed it to use the part’s lookvector instead of the torso so I can show it changing in the first video.

I just used dot product for getting the angle so its limited to 180 degrees. But you can use a better method for getting the angle if you need it for 360 degrees.

In this video, I apply it to the actual torso. So if the torso rotates (uppertorso cuz r15) then my camera rotates by that amount as well. (if part rotates 15 degrees, then 15 degrees is added to my current camera).

Because of the run animation on my character, you can see it bobbing up and down because of it.

Code if it helps

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer

repeat task.wait() until player.Character
local character = player.Character

local root = character:WaitForChild("HumanoidRootPart")
local torso = character:WaitForChild("UpperTorso") -- assuming r15

local camera = workspace.CurrentCamera

local previousAngle
local angle = 0

local function getTorsoRoll()
	local baseVector = Vector3.new(torso.CFrame.LookVector.X, 0, torso.CFrame.LookVector.Z).Unit
	
	local torsoVector = torso.CFrame.LookVector
	
	local dot = baseVector:Dot(torsoVector)
	
	angle = math.acos(dot)
end

RunService.Heartbeat:Connect(function(dt)
	getTorsoRoll()
	
	-- this is so we are only adding on the difference
	local diff = 0
	if previousAngle then
		diff = angle - previousAngle
	end
	previousAngle = angle
	
	camera.CFrame = camera.CFrame * CFrame.Angles(diff, 0, 0)
end)


1 Like

I’ll look into utilizing the LookVector & dot function to accomplish my goals here, I’ll be back to share any progress, realistically probably tomorrow.

Thank you for the help so far.

The HumanoidRootPart’s CFrame already has the world rotation, so you just need to add the mouse offsets.

local function onMouseMove(input)
	local delta = input.Delta
	
	cameraYaw = cameraYaw - delta.X * MOUSE_SENSITIVITY
	cameraPitch = cameraPitch - delta.Y * MOUSE_SENSITIVITY
	
	cameraPitch = math.clamp(cameraPitch, -PITCH_LIMIT, PITCH_LIMIT)
end
local function updateCamera()
	local character = player.Character
	if not character then return end
	
	local hrp = character:FindFirstChild("HumanoidRootPart")
	if not hrp then return end
	
	local bodyCFrame = hrp.CFrame * CFrame.new(0, 2, 0) --at head height

	local mouseOffset = CFrame.fromEulerAnglesYXZ(
		math.rad(cameraPitch),
		math.rad(cameraYaw),
		0
	)

	camera.CFrame = bodyCFrame * mouseOffset
end
1 Like

Thank you everyone for your support in trying to solve the issue, I finally found a formula which achieved the intended functionality. Here’s the Update_View function in case anyone was curious.

local function Update_View ()
	local head:Part = assignedCharacter.Head
	local torso:Part = assignedCharacter.HumanoidRootPart
	local displayHead:Part = assignedCharacter.DisplayHead
	local headCF = head.CFrame
	
	if math.abs(forwardPressed) == 1 or alignTorsoWithHead == true then
		torsoYaw = math.lerp(torsoYaw,viewportVector.X,Config.movement.torsoLerpAlpha)
	end
	
	torso.CFrame = CFrame.new(torso.Position) * torso.CFrame.Rotation:Lerp(CFrame.fromEulerAnglesYXZ(math.rad(torso.Orientation.X), math.rad(torsoYaw), math.rad(torso.Orientation.Z)), Config.camera.lerpAlpha)
	
	--displayHead.CFrame = CFrame.new(headCF.Position) * displayHead.CFrame.Rotation:Lerp(CFrame.fromEulerAnglesYXZ(math.rad(viewportVector.Y), math.rad(viewportVector.X), 0), .3) old "gimbal locked" code.
	displayHead.CFrame = head.CFrame * CFrame.fromEulerAnglesYXZ(math.rad(viewportVector.Y), math.rad(viewportVector.X - torsoYaw) , 0)
	
	assignedCamera.CFrame = assignedEyes.WorldCFrame
end

Thank you all for your time!!!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.