Make Part Match Part Below

Hi! Sorry but this is kinda complex to explain without showing screenshots.

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

I want to make it so a part changes rotation based on the part it’s on top of, like when on a hill, I want it to match that hill.

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

What I want is for parts to be able to turn left and right to change direction the direction it’s going for (already done) but whenever it’s on a hill or something elevated it doesn’t stay straight, it continues looking the way as if it weren’t. I am currently using BodyGyro to keep it straight.

Here’s an example of what it going up a hill should look like:

What it looks like now:
image

How I want it to look:
image

I want to make extra clear though, I still want it to be able to rotate like this:

image

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

I tried to find something on the forums but the closest I find involved using Humanoids. I will not be using Humanoids just for a part.

I have tried making the BodyGyro’s CFrame the CFrame of the part below (using raycasting) but that makes it match the rotation of the part below, which prevents it from turning.

Thank you so much for reading!

You can get the rotation matrix of the bottom part, then add that to the position of bottom part and the upvector of the bottom part to the CFrame of the top part to put it directly on top of the bottom part

then you can rotate it using CFrame.Angles

local bp = workspace.BottomPart
local tp = workspace.Part

local rot = (bp.CFrame - bp.CFrame.Position)--getting the rotational matrix
local pos = bp.CFrame.UpVector * bp.Size.Y--getting the up vector * y size

tp.CFrame = rot + pos + bp.Position * tp.Size.Y/2 + bp.CFrame.UpVector/2 --adding together the pieces

wait(5)

tp.CFrame = tp.CFrame*CFrame.Angles(0,1,0) --rotation

Addition: make sure to multiply the UpVector by the Y size of the bottom part, as the upvector is a unit vector and only has a magnitude of 1

Addition 2: Made it compatable to all Y sizes for the top part

If you have any questions/ clarifications, be sure to let me know :slight_smile:

3 Likes

You were on the right track by raycasting. The FindPartOnRay methods return a surface normal if there’s an intersection. You can use this surface normal to set one of the named directional components of a cframe, like upVector in your example, as well as with CFrame.fromAxisAngle to allow for the plane locked rotation like your last picture.

1 Like

So just set BottomPart to whatever the ray touches?

Yes, assuming that ray is the ray that is supposed to find the bottom part

Alright, I’m gonna get this working with my raycasting stuff goes below the part after I rest up a bit, and once I get it done, if it works I will 100% slam that solution button on your post, thank you so much for your help!

His solution works for your specific example scenario, but it’s basically just solving for the position and normal values that the ray intersection already returns as mentioned above.

Hard coding things like that can be invaluable for developing spatial math intuition, but his code snippet will only work when the ground part is oriented so the top surface is the surface of intersection.

You probably want something that works in the 5 other possible orientations, so you really should consider exploring the concepts mentioned in my reply. I purposefully omitted any code samples to help steer you towards developing an understanding instead of just plugging something in.

Also using the surface normal requires adding a single operation to your existing cast/cframe assignment code :wink:

Okay so this only did the same thing as I did, it’s still rotating it onto it’s side.

I want it to still be able to turn left and right. This makes it so it rotates to stand on the surface, but still it won’t let it turn left or right.

I tried timing it, like making it fire every 5 seconds, but every five seconds it turns it to the side once again.

Any solution?

You are using a gyro right? you will probably need to change that not just set the CFrame.

What do you mean? I’m setting the CFrame of the gyro. Is there something else that needs changed?

I would need to test gyros (haven’t used them before) and I don’t have the time at the moment.

If the forces used to maintain orientation are applied in object space you could make Y=0 so you can freely rotate without the gyro changing it.

I’m not sure what to do if the rotation is applied on the world axis :confused:

Manual rotation would need to be object space too.

Alright I give up trying

here’s your code

local runSer = game:GetService('RunService')
local uis = game:GetService('UserInputService')

local part = Instance.new('Part')
part.Anchored = true
part.Parent = workspace
part.TopSurface = Enum.SurfaceType.Smooth
part.BottomSurface = Enum.SurfaceType.Smooth
part.Color = Color3.new(1,0,0)
part.CanCollide = false

local cam = workspace.CurrentCamera

local rotation = 0
local rdown = false
local rotstep = math.rad(2)

runSer.RenderStepped:Connect(function()
    if uis:IsKeyDown(Enum.KeyCode.R) then
        rotation = rotation + rotstep
        if rotation > 2*math.pi then
            rotation = rotation- 2*math.pi
        end
    end
    
    local screenpos = uis:GetMouseLocation()
    local ray = cam:ViewportPointToRay(screenpos.X, screenpos.Y)
    ray = Ray.new(ray.Origin, ray.Direction * 1500)
    
    local hitpart, pos, norm = workspace:FindPartOnRayWithIgnoreList(ray, {part})
    
    if hitpart then
        part.CFrame = (CFrame.new(pos, pos - norm) * CFrame.Angles(math.rad(90), rotation, 0)) * CFrame.new(0, part.Size.Y/2,0)
    end
end)
12 Likes

surface normals are pretty rad
tl;dr: create a new cframe using the lookvector:cross(norm) as the rightvector, the surface normal as the upvector, and the lookvector being the same.

-- bp is the part to rotate, rcf is the old cframe (bp.CFrame in this case)
local nray = Ray.new((rcf * CFrame.new(0,bp.Size/2,0)).p,-rcf.upVector*50) -- create ray below bp (the hovercraft, humanoid you're rotating, whatever)
local rval = {workspace:FindPartOnRay(nray,bp)} -- ray to fire below
if rval[1] then -- if the ray hit a part...
  local norm = rval[3] -- surface normal of ray
  local npos = rval[2] + (norm*3) -- this is so the craft floats above the ray's pos slightly
  
  -- tcf is the new cframe thats aligned to the ground
  local tcf = CFrame.fromMatrix(npos,rcf.lookVector:Cross(norm),norm,-rcf.lookVector)
  rcf = tcf -- set craft cframe to rotated cf.
end
bp.CFrame = rcf
11 Likes