-- 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)
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 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