Anyone here familiar with trigonometry or CFraming want to help? You will benefit millions

I have a game called downhill rush Downhill Rush - Roblox
It’s working nicely however the map involves twists and turns and I am trying to keep the car stuck to the road. It’s a little frustrating yknow?
(if you press T and look in the console it gives u the angle called desiredroll)

Anyway, when the player is going down the hill, a raycast is shot downwards in the direction of the gravity and this raycast returns a normal.

I use this normal and compare it to the UpVector of what is called prevface. “prevface” is simply a part that is aligned with the road that tells the players cart which way to face or go.

My intention is to have the cart’s ROLL or Z angle be the same as the angle between the ray normal and the upvector of prevface.

I have made following code and put 3 parts in the workspace to test it out. Each part is called a, b, c

prevface = workspace.b
upvec = workspace.a.CFrame.UpVector
                local function getRotationBetween(u, v, axis)
                    local dot, uxv = u:Dot(v), u:Cross(v)
                    if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
                    return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
                end
                local randomAxis = Vector3.new(1,0,0)
            
                local rotateToFloorCFrame = getRotationBetween(prevface.CFrame.UpVector,upvec,randomAxis) 
         local x,y,goalangle = (rotateToFloorCFrame * prevface.CFrame):ToEulerAnglesXYZ() 
workspace.c.Position = workspace.b.Position
 workspace.c.CFrame = prevface.CFrame * CFrame.Angles(0,0,goalangle + math.pi) 
workspace.c.Position = workspace.c.Position + Vector3.new(0,5,0)

The red block is named “a” has an UpVector that is pointing upwards, this represents the ray normal to the ground
The green block is named “b” and this is the “prevface” which is directly in alignment with the road

The blue block is named “c” and this is going to rotate in the same direction as the ray normal. NOT the same direction as the part a because we are only using part a’s ray normal

Here are difference scenarios:
All the parts are aligned nicely, so in the game the floor would be flat and the cart is perfectly aligned to the road
image

We pitch the red part a tiny bit forward now, the blue part is perfectly aligned with the road because the upvector is aligned with the upvector of the green part
image

We tilt the red part a bit to the side, as this represents the player driving along a curve of the road, all good
image

Now here’s the tricky part
The road will turn left and right as well as going up and down for more fun in the game.

This looks good to me, perfectly aligned
image
image

Now here’s an issue: The green part represents the general direction of the road, the road is going down at 10 degrees and may occasionally rotate to the left or right relative to that grey part you see which is aligned with the world at an orinetation of 0,0,0…

image

So now why isn’t the blue part aligned with the red part in this case?

image

A scenario where the cart is driving along a curve too in a hill turn
image

Those images don’t seem to be loading for me, is that an issue on my end or can you try to fix that?

1 Like

Hello there!

At the time I´m writing this, the posted images aren´t visible, but I´ll try my best to give advice from what I understand from your post.

So for the alignment problem, I´d recommend checking out AlignOrientation. For the CFrame math, I recommend checking out this forum post.

Hope you find these resources helpful.

Should the cart’s UpVector always just match the road surface normal?

I’m confused about how the green part (prevface) comes into this at all—does it determine the cart’s LookVector or something?

the green part is the direction of the road. It determines the cart’s look vector yes.

The cframe of the cart is.

prevface.CFrame * CFrame.Angles( 0, yawangle, rollangle )

We just need to define roll angle to be the same as the ray.Normal

Here’s two options (I’m slightly changing the names here, cart is workspace.c, road is workspace.b, n is workspace.a.CFrame.UpVector)

One, setting cart.CFrame to road.CFrame and then projecting your LookVector to the road surface by projecting it vertically:

local function AlignUpVector(cf: CFrame, up: Vector3)
	-- project LookVector to plane vertically
	local look = cf.LookVector
	look = look - look:Dot(up)*up
	local right = look:Cross(up)
	
	local pos = cf.Position
	return CFrame.fromMatrix(pos, right, up, -look)
end

cart.CFrame = AlignUpVector(road.CFrame + Vector3.new(0, 5, 0), n)

Second, you could set cart.CFrame to road.CFrame, and rotate by the angle just along the road’s Z axis (I think you wanted this one):

-- bad name for this method but whatever
local function RotateZAxis(cf: CFrame, up: Vector3)
	local objSpace = cf:VectorToObjectSpace(up)
	local angle = math.atan2(-objSpace.X, objSpace.Y)
	return cf * CFrame.Angles(0, 0, angle)
end

cart.CFrame = RotateZAxis(road.CFrame + Vector3.new(0, 10, 0), n)

Examples (blue is the first option. yellow is the second)