CFrames Help With Advanced CFrame Look At Function

I’m trying to make the ‘aimAttachment’ point towards a target position by pivoting the Waist Motor6D.

Here are some diagrams I’ve created that should help demonstrate my issue.


Here is some code:

-- LocalScript located in: 'game.StarterPlayer.StarterCharacterScripts'.

local players = game:GetService("Players")

local localPlayer = players.LocalPlayer
local mouse = localPlayer:GetMouse()
	
local character = script.Parent
local lowerTorso = character:WaitForChild("LowerTorso")
local upperTorso = character:WaitForChild("UpperTorso")
local waist = upperTorso:WaitForChild("Waist")

-- Attachment will be in your player's upperTorso. This is used for the lookAtOffset.
local aimAttachment = Instance.new("Attachment", upperTorso)
aimAttachment.Position = Vector3.new(0.4, 0.5, -1.5)
aimAttachment.Name = "aimAttachment"

local initialWaistC0 = waist.C0

-- Need help here. --
function calculatePivotingOffsetLookAt(from:Vector3, to:Vector3, pivot:Vector3, lookAtOffset:CFrame):CFrame
	return CFrame.lookAt(from, to)
	-- make the aimAttachment look at 'to' using the lookAtOffset whilst pivoting at 'from'
end
---------------------

game:GetService("RunService").RenderStepped:Connect(function()
	local target = (lowerTorso.CFrame * initialWaistC0):ToObjectSpace(mouse.Hit)
	
	waist.C0 = calculatePivotingOffsetLookAt(initialWaistC0.Position, target.Position, waist.C1, aimAttachment.CFrame)
end)

The correct function for this should replace calculatePivotingOffsetLookAt. If there are ways to accomplish this that use different parameters, that should work too.

I’ve been struggling on this for 2 days straight, help would be extremely appriciated, thanks!

I’m pretty sure that calculating this kind of CFrame is impossible when the distance between target and waist is less than the distance between waist and the line going through aimAttachment in the direction of aimAttachment’s look vector (the latter distance is constant unless aimattachment or waist is moved relative to upper torso). This is because target needs to be on that line but it can’t be on the line if it’s closer to waist than waist is to the line.

In the code, I use the ratio of these distances (waist signed distance from the line and target distance from waist) and I just clamp it between -1 and 1 but there could be a better way to handle this case.

Also, do you have a reason for changing C0 instead of changing Transform? Changing Transform lets you prevent animations from interfering with this. And it is apparently also more efficient. Transform must be changed on RunService.PreSimulation / RunService.Stepped so that animations don’t override it.

Quote from documentation:

Motor6D transforms are not applied immediately, unlike updating C0 and C1, but rather as a batch in a parallel job after RunService.PreSimulation, immediately before physics steps. The deferred batch update is much more efficient than many immediate updates.

Here’s the code change. There might be a simpler way to achieve this but this is the best I could come up with.

local function calculatePivotingOffsetLookAt(to: Vector3, pivot: Vector3, lookAtOffset: CFrame): CFrame
	local horizontalLookAtCF: CFrame
	if to:Cross(Vector3.yAxis).Magnitude < 1e-4 then
		horizontalLookAtCF = CFrame.identity
	else
		horizontalLookAtCF = CFrame.lookAt(Vector3.zero, Vector3.new(to.X, 0, to.Z))
	end

	local angle1: number = math.asin(to.Y / to.Magnitude)

	local waistSignedDistFromLookAtAttachmentInLookAtAttachmentDownDirection: number = (lookAtOffset.Position - pivot):Dot(lookAtOffset.UpVector)
	local cosineOfAngle2: number = math.clamp(waistSignedDistFromLookAtAttachmentInLookAtAttachmentDownDirection / to.Magnitude, -1, 1)
	local angle2: number = math.acos(cosineOfAngle2)

	local upperTorsoUpVectorAngleFromLowerTorsoUpVector: number = -(.5 * math.pi - angle1 - angle2)

	local transform: CFrame = horizontalLookAtCF * CFrame.Angles(upperTorsoUpVectorAngleFromLowerTorsoUpVector, 0, 0)
	return transform
end

RunService.PreSimulation:Connect(function()
	local target = (lowerTorso.CFrame * initialWaistC0):ToObjectSpace(mouse.Hit)
	
	waist.Transform = calculatePivotingOffsetLookAt(target.Position, waist.C1.Position, aimAttachment.CFrame)
end)

Here’s an image explaining the calculations.

1 Like

Thanks @RoBoPoJu, your function is awesome! Unfortunately, I do blame myself for this as I was not clear enough, but you did not account for the X offset which I had on the attachment.

To further explain what I’m looking for, I created some beams and put my face up to a wall to show you where your script is achieving relative to where I want it to be.


The red beam is what I want. The cursor is where your script got. The blue beam is just to show you’ve achieved the Y offset.

I’m not sure how important Z offset is for what I’m doing but If you know how to get both the X and Y offset, that would work for me.

I’ve made a script that calculates the world position of the attachment as if there were no animations or changes to C0. Using C0 was intended as I plan to have animations that move the waist as well.

I wasn’t aware of this, thank you!

I seem to have finally managed to write a function that works with x offset and even with rotated aimAttachment although the latter doesn’t seem to be a requirement in your case. The code calculates a CFrame that rotates waist such that target will be on the line that goes through aimAttachment in the direction of its LookVector as long as some minimum distance requirements are satisfied.

local function calculatePivotingOffsetLookAt(target: Vector3, pivot: Vector3, lookAtOffset: CFrame): CFrame
	local horizontalLookAtCF: CFrame
	if target:Cross(Vector3.yAxis).Magnitude < 1e-4 then
		horizontalLookAtCF = CFrame.identity
	else
		horizontalLookAtCF = CFrame.lookAt(Vector3.zero, Vector3.new(target.X, 0, target.Z))
	end

	-- calculating angle around y-axis
	local xzTarget: Vector3 = Vector3.new(target.X, 0, target.Z)
	local verticalRotationAxisUnitVector: Vector3 = lookAtOffset.LookVector:Cross(Vector3.yAxis).Unit
	
	local waistSignedDistFromAimAttachmentInAimAttachmentLeftDirection: number = (lookAtOffset.Position - pivot):Dot(verticalRotationAxisUnitVector)	
	local sineOfXZAngle1: number = math.clamp(waistSignedDistFromAimAttachmentInAimAttachmentLeftDirection / xzTarget.Magnitude, -1, 1)
	local xzAngle1: number = math.asin(sineOfXZAngle1)

	local xzAngle2: number = math.atan2(lookAtOffset.LookVector.X, -lookAtOffset.LookVector.Z)

	local angleAroundYAxis: number = xzAngle1 + xzAngle2

	-- calculating vertical angle
	-- Vertical rotation plane is not necessarily parallel to the line between waist and target.
	local targetDistInVerticalRotationPlane: number = math.sqrt(target.Y^2 + (math.cos(xzAngle1) * xzTarget.Magnitude)^2)
	local verticalAngle1: number = if math.abs(target.Y) > math.abs(targetDistInVerticalRotationPlane) then .5 * math.pi else math.asin(target.Y / targetDistInVerticalRotationPlane)
	
	local angleOriginPointSignedDistFromAimAttachmentInDirectionOfVerticalRotationPlaneParallelNormalOfAimAttachmentLookVector: number = (lookAtOffset.Position - pivot):Dot(verticalRotationAxisUnitVector:Cross(lookAtOffset.LookVector))
	local cosineOfYZAngle2: number = math.clamp(angleOriginPointSignedDistFromAimAttachmentInDirectionOfVerticalRotationPlaneParallelNormalOfAimAttachmentLookVector / targetDistInVerticalRotationPlane, -1, 1)
	local verticalAngle2: number = math.acos(cosineOfYZAngle2)

	local verticalAngle3: number = math.asin(lookAtOffset.LookVector.Y)

	local verticalAngle: number = -(.5 * math.pi - verticalAngle1 - verticalAngle2) - verticalAngle3

	local transform: CFrame = horizontalLookAtCF * CFrame.fromEulerAnglesYXZ(0, angleAroundYAxis, 0) * CFrame.fromAxisAngle(verticalRotationAxisUnitVector, verticalAngle)
	return transform
end
1 Like