Change only a CFrame upVector

Can anyone tell me how to change the UpVector of a CFrame without changing the relative Y axis rotation?

1 Like

Can you elaborate on your question? It’s not making sense right now.

I’m not really sure how to ask it any differently.

Consider a block that spins like a top left/right. The upvector points to the sky. Now change the upvector to point toward world forward. But not change the degree it was already rotated relative.

Hard to articulate this.

You have a start and target upvector:

local start = part.CFrame.UpVector
local target = Vector3.new(1, 1, 1).Unit

You can do this in a single rotation about an axis. The axis is perpendicular to both:

local axis = start:Cross(target)

And figure out the angle between the two

local angle = math.acos(start:Dot(target))

Then just rotate the CFrame by that amount

part.CFrame = CFrame.fromAxisAngle(axis, angle) * part.CFrame.Rotation + part.CFrame.Position

I’ll do some testing to check, but I’m pretty sure that would work. Ignoring cases like full 180 degree rotations or 0 degree rotations, for which you’ll have to write special cases.

Edit: left out one part, gimme a sec :slight_smile: Fixed. Final function:

local function AlignUpVector(cframe: CFrame, up: Vector3)
	local start = cframe.UpVector
	local angle = math.acos(start:Dot(up))
	local axis = nil
	if angle < 0.00001 or angle > math.pi - 0.00001 then
		axis = Vector3.xAxis
	else
		axis = start:Cross(up)
	end
	return CFrame.fromAxisAngle(axis, angle) * cframe.Rotation + cframe.Position
end

Example use:

local target = Vector3.new(1, 1, 1).Unit
part.CFrame = AlignUpVector(part.CFrame, target)

Edit: Modified function to take into account special cases

6 Likes

It might help too if I explain what I’m trying to accomplish.

Basically, I’m trying to detect how much the block spins on the relative Yaxis. I can do this accurately while the upvector is world up. However the upvector is constantly changing. So I was thinking I could translate to the world up vector then grab the current relative Yaxis orientation to see what the change in orientation was.

Ty I think this may work. I’m going to do some testing and verify I can accomplish my goal with it. Looks promising.

Yeah, I think you’d be able to align the previous CFrame’s upvector with the current CFrame’s upVector and then compare the angle between their right vectors?

local function GetSpin(prev: CFrame, current: CFrame)
  local aligned = AlignUpVector(prev, current.UpVector)
  return math.acos(aligned.RightVector:Dot(current.RightVector))
end
1 Like

Tyvm and kudos your trig game is def better than mine. :grin:
I should be able to accomplish the end goal with this.

1 Like

Fixing it:

local function AlignUpVector(cframe: CFrame, up: Vector3)
	local start = cframe.UpVector
	local angle = math.acos(start:Dot(up))
	local axis = nil
	if angle < 0.00001 or angle > math.pi - 0.00001 or angle ~= angle then
		axis = Vector3.xAxis
	else
		axis = start:Cross(up)
	end
	return CFrame.fromAxisAngle(axis, angle) * cframe.Rotation + cframe.Position
end

added the angle~= angle cause it sometimes returns nan which glitches it.

Angle should never be NaN

You may be giving the function a non-Unit vector, which can cause NaN

You probably want to call .Unit on your Vector before passing it to this function

Otherwise the angle will be wrong or NaN

1 Like