How do I rotate an object on an inclined axis?

Basically, I have an object that is tilted on the X and Z axes and I want to rotate it on the Y axis. When I do this, it’s rotating on the WORLD Y axis. How do I get it to rotate on it’s own Y axis. Here’s the rotation script that I have.

local model = script.Parent

local partsList = {
	{
		part = model:FindFirstChild("Lowest Layer-a");
	};
	{
		part = model:FindFirstChild("Lowest Layer-b");
	};
	{
		part = model:FindFirstChild("Mid-Lower Layer");
	};
	{
		part = model:FindFirstChild("Mid-Upper Layer");
	};
	{
		part = model:FindFirstChild("Outer Layer");
	};
	{
		part = model:FindFirstChild("Rings 1");
	};
	{
		part = model:FindFirstChild("Rings 2");
	};
	{
		part = model:FindFirstChild("Surface");
	};
}



for _, item in ipairs(partsList) do
	local cframe = item.part:GetPivot()
	item.pos = cframe.Position
	local ax, ay, az = cframe:ToOrientation()
	item.tx = math.deg(ax)
	item.ty = math.deg(ay)
	item.tz = math.deg(az)
	local rotate = item.part:GetAttribute("Rotate")
	item.rx = rotate.X
	item.ry = rotate.Y
	item.rz = rotate.Z
end

task.spawn(function()
	local mathrad = math.rad
	local cframe
	local dt
	while true do
		dt = task.wait(0)
		for _, item in ipairs(partsList) do
			item.tx = (item.tx + (item.rx * dt)) % 360
			item.ty = (item.ty + (item.ry * dt)) % 360
			item.tz = (item.tz + (item.rz * dt)) % 360
			cframe = CFrame.new(item.pos) *
				CFrame.Angles(mathrad(item.tx), mathrad(item.ty), mathrad(item.tz))
			item.part:PivotTo(cframe)
		end
	end

end)

This is a planet that has a tilted axis in respect to the world Y axis. Think of it like the planet Earth which rotates on an inclined axis of 23° from true.

image

I figure I might have to use local space but I’m not sure what that looks like in CFrames.

2 Likes

You want to use CFrame:ToWorldSpace() to rotate the thing on its own axes. You could probably do this

item.part:PivotTo(item.part:GetPivot():ToWorldSpace(cframe))

This rotates your part in the worldspace by 90 degrees on the Y axis vvv

--[[
	Basically this adds your angle offset to the parts current orientation to get the correctly rotated cframe
	And then adds that rotated cframe onto the parts position
]]
local myPart = workspace.MyPart
local cframeAngle = CFrame.Angles(0, math.rad(90), 0)
local rotatedCFrame = cframeAngle * CFrame.fromOrientation(myPart.CFrame:ToOrientation())
myPart.CFrame = CFrame.new(myPart.CFrame.Position) * rotatedCFrame --add the rotatedCFrame onto the position

1 Like

The way I do this is:

local rotateDegreesY = 45
local rotationCFrame = CFrame.Angles(0, math.rad(rotateDegreesY), 0)
object.CFrame = object.CFrame:ToWorldSpace(rotationCFrame)

The reason this works is because you create a CFrame rotated around the Y axis in world space, then you convert that from CFrame space to world space.

In other words, a rotation around Y relative to the CFrame in world space is a relative rotation around Y.

When I ran this it rotated like if you did this:

local rotationCFrame = CFrame.Angles(0, math.rad(rotateDegreesY), 0)
object.CFrame = object.CFrame * rotationCFrame
2 Likes

this. right-multiplication with CFrame.Angles is the simplest solution, as that applies the transformation locally (as opposed to left-multiplication which transforms it globally)

2 Likes

I was thinking that’s what the OP was looking for, but I could have misinterpreted something.

Here’s what that looks like in studio for the @OP:

ScreenRecording2024-08-27at6.11.02PM-ezgif.com-video-to-gif-converter

Currently, the OP’s code uses the same rotation method, but is set up for something different:

(tx, ty, and tz are parts of the Part’s CFrame’s orientation)

This code rotates the unrotated position CFrame, but adding to the item.ty value just rotates it on the global axis, since the position CFrame on the left of the multiplication is unrotated.

1 Like

That is exactly what I was looking to achieve. However, doing some research, I came up with this code:

-- Prepare Data
for _, item in ipairs(partsList) do
	item.cframe = item.part:GetPivot()
	local rotate = item.part:GetAttribute("Rotate")
	item.rx = rotate.X
	item.ry = rotate.Y
	item.rz = rotate.Z
end

-- Run
task.spawn(function()
	local mathrad = math.rad
	local dt, tx, ty, tz
	while true do
		dt = task.wait(1/30)
		for _, item in ipairs(partsList) do
			tx = CFrame.Angles(mathrad(item.rx * dt), 0, 0)
			ty = CFrame.Angles(0, mathrad(item.ry * dt), 0)
			tz = CFrame.Angles(0, 0, mathrad(item.rz * dt))
			item.cframe = item.cframe * tx * ty * tz
			item.part:PivotTo(item.cframe)
		end
	end
end)

So basically, it gets the rotate vector for all the listed parts (the vector value units are degrees per second). Then it calculates the angle increment based on the time delay and applies it to the object CFrame in <X, Y, Z> order. The original code that I posted was adapted from other code that I wrote a couple of years ago to rotate an object on the world <X, Y, Z> axes. This one needed to rotate on the local Y axes like Earth does.

The requirement necessitates the ability to rotate around any X, Y, or Z axis in any combination. Plus, this is independent of the orientation of the object itself. No matter what the orientation of the object is, it will always rotate on the specified local axes.

1 Like

You can use combine two consecutive CFrame.Angles in a row, after each other to set the cframe which is then moved by next angle.

local desiredAngleOnAxis = CFrame.Angles(math.rad(23), 0, 0) * CFrame.Angles(0, math.rad(30), 0)

Experiment with these axis to fit your needs.