Hi! Sorry but this is kinda complex to explain without showing screenshots.
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.
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:
How I want it to look:
I want to make extra clear though, I still want it to be able to rotate like this:
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.
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
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.
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
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)
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