Issues with Derived World Data Axes when trying to make a bone object rotate to face the position of another object

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!

My goal is to be able to have a bone instance of a model correctly rotate to the position of an instance in the workspace be it a part or a model similar to the CFrame.lookAt() method.

  1. What is the issue? Include screenshots / videos if possible!

Because the bones need a specific axis for animations, their axes are derived by default, so when you try to rotate something on the Y axis hoping it would go left to right, it’ll, due to the new axis, rotate at an angle. Keep in mind this axis cannot be changed due to animations of the model.

  1. What solutions have you tried so far? Did you look for solutions on the Creator Hub?

I’ve tried a custom CFrame.lookAt() implementation using atan2 to translate the direction of the part to be tracked relative to the bones position. This should give me more flexibility in how I want to rotate the part be it CFrames, WorldCFrames, CFrame.fromAxis, manipulations of the bone CFrame in relation to the models space. However, none have worked. The closest I got was manipulating the WorldCFrame, however when orienting the model at a different Y angle, it all breaks

Here’s the entire code that runs the tracking implementation with WorldCFrames, you will most likely see the issue right away, but the solution is unbearably hard and I’ve yet to find it.

assume IKConfig.Yaw is just math.rad() angles, same for IKConfig.Pitch and IKConfig.TurnSpeeds returns a number

local replicatedStorage = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")
local playerService = game:GetService("Players")

local IKConfig = require(replicatedStorage.Configs.IKConfig)

local signal = runService.Heartbeat
local tracking = replicatedStorage.States.Tracking

local bones = {}
local originalCFrames = {}
local currentYaws = {}
local currentPitchs = {}

local NUM_OF_ACTIVES = 5 - 1

local function GetBones()
	local model = workspace:WaitForChild("A_Running").Dinosaur
	
	bones["FirstSpine"] = model.DeformationSystem.root.Root_M.Spine1_M.Spine2_M.Chest_M
	bones["SecondSpine"] = model.DeformationSystem.root.Root_M.Spine1_M.Spine2_M
	bones["ThirdSpine"] = model.DeformationSystem.root.Root_M.Spine1_M
	bones["Neck"] = model.DeformationSystem.root.Root_M.Spine1_M.Spine2_M.Chest_M.Neck0_M
	bones["Head"] = model.DeformationSystem.root.Root_M.Spine1_M.Spine2_M.Chest_M.Neck0_M.Neck1_M.Neck5_M.Head_M
end

local function OrientBone(direction, boneInstance, boneName, deltaTime)
	if not originalCFrames[boneName] then
		originalCFrames[boneName] = boneInstance.CFrame
		currentYaws[boneName] = 0
		currentPitchs[boneName] = 0
	end

	local directionFlat = direction * Vector3.new(1, 0, 1)

	local targetYaw = math.atan2(directionFlat.X, directionFlat.Z)
	targetYaw = math.clamp(targetYaw, -IKConfig.Yaws[boneName], IKConfig.Yaws[boneName])

	local horizontalDistance = directionFlat.Magnitude
	local targetPitch = math.atan2(direction.Y, horizontalDistance)
	targetPitch = math.clamp(targetPitch, -IKConfig.Pitches[boneName], IKConfig.Pitches[boneName])

	currentYaws[boneName] = currentYaws[boneName] + (targetYaw - currentYaws[boneName]) * math.min(deltaTime * IKConfig.TurnSpeeds[boneName], 1)
	currentPitchs[boneName] = currentPitchs[boneName] + (targetPitch - currentPitchs[boneName]) * math.min(deltaTime * IKConfig.TurnSpeeds[boneName], 1)

	boneInstance.CFrame = originalCFrames[boneName]
	
	local pivot = boneInstance.WorldCFrame.Position
	boneInstance.WorldCFrame = CFrame.new(pivot) * CFrame.Angles(-currentPitchs[boneName] / NUM_OF_ACTIVES, currentYaws[boneName] / NUM_OF_ACTIVES, 0) * (boneInstance.WorldCFrame - pivot)
end

local function Update(deltaTime)
	local player = playerService:GetPlayerByUserId(tracking.Value)
	if not player then return end

	local character = player.Character
	if not character then return end

	local targetPosition = character:GetPivot().Position

	for boneName, bone in pairs(bones) do
		OrientBone( ( bone.WorldCFrame.Position - targetPosition ), bone, boneName, deltaTime )
	end
end

local function Setup(data)
	GetBones()
	
	signal:Connect(Update)
end

shared.Controllers.LoadController.register_loaded("TrackingSetup", Setup)

return nil

If you need more context or information on the dinosaurs structure, or a clearer explanation of my goal don’t hesitate to ask. Thank you in advance for any help

This may be me being dumb, but you rotated that model on your client. That will not replicate, the server does not recognize the rotation.

If that is a server script, this seems to be your issue.

No you’re right that I rotated it on the client, however due to network optimizations, I decided to replicate it to all clients, or else simply head tracking would eat up 20kbps of network receiving.