How to get model to rotate along bottom surface

Atm, I’ve got movement working while keeping the model to stay where the bottom surface is always facing down. But I want to have it rotate along walls/roof/other surfaces.

This how it currently looks

And this is how I want it to work (from Adopt Me)

I am using raycast, so I should be able to get the normal by doing NewRay.Normal but I’m not sure how to include it, without it ruining the current the movement

local RaycastFilter = RaycastParams.new()
RaycastFilter.FilterType = Enum.RaycastFilterType.Blacklist
RaycastFilter.FilterDescendantsInstances = {Model, Player.Character, workspace.Plots.Interiors[Player.Name].Build.Door}

local UnitRay = Camera:ScreenPointToRay(Mouse.X, Mouse.Y, 1)
local NewRay = workspace:Raycast(UnitRay.Origin, UnitRay.Direction * 1000, RaycastFilter)

if not NewRay.Instance then return end -- Make sure instance exists

-- Snap to 0.5 grid
local PosX = math.floor((NewRay.Position.X / 0.5) + 0.5) * 0.5
local PosY = math.floor((NewRay.Position.Y / 0.5) + 0.5) * 0.5
local PosZ = math.floor((NewRay.Position.Z / 0.5) + 0.5) * 0.5

local NewPos = Vector3.new(PosX, PosY, PosZ)
	
local Pos = (Model.PrimaryPart.CFrame - Model.PrimaryPart.Position) + NewPos
Model:SetPrimaryPartCFrame(
	Pos * -- Set position
	CFrame.new(0, Model.PrimaryPart.Size.Y / 2, 0) -- Offset the model so its not halfway through the floor
)
1 Like

I suggest first determining which face (of the wall etc) you hit then rotate it accordinatly.

How can I do that???

You can use the Mouse.Target. This is what I would do.
1.) Set the floor part, maybe name it floor, and if the mouse.Target.Name is floor, then the orientation is always (0, Specified Angle, 0)
2.) Set wall parts, maybe name it wall, and if the mouse.Target.Name is wall then the orientation is (Orientation that you want, Angle that player specified, 0)
3.) To get the orientation that you want, use the position and then use math to detect which side of the wall it is on.

I suggest using raycasting and using after that RaycastResult, you can determine with RaycastResult.Normal how you should rotate the object you’re moving.
By making the direction of the normal the top side.

Note, you’ve to edit to rotate the CFrame offset of the model as well with this.

Yes, if you read my code you’d see I am using raycasting, and I am aware of how to get the normal. My question tho is how can I implement that into what I currently have

Mouse.Target isn’t that great for this kinda stuff. Raycasting has all the necessary objects returned

You would need to do some vector math using the cross product. Yes, you will have to use raycasting for this and use the surface normal of the part the ray has hit:

local axis = Vector3.new(0, 1, 0):Cross(normal)
local angle = math.asin(axis.Magnitude)

if cross.Magnitude == 0 then
    axis = Vector3.new(1, 0, 0)
end

Model:SetPrimaryPartCFrame(CFrame.new(pos + normal * Model.PrimaryPart.Size.Y / 2) * CFrame.fromAxisAngle(axis, angle))

You haven’t specified what ‘cross’ is

if cross.Magnitude == 0 then

cross was meant to be axis

Got this error on the SetPrimaryPartCFrame line
invalid argument #1 to ‘new’ (Vector3 expected, got CFrame)

local Pos = (Model.PrimaryPart.CFrame - Model.PrimaryPart.Position) + NewPos
	
local axis = Vector3.new(0, 1, 0):Cross(NewRay.Normal)
local angle = math.asin(axis.Magnitude)

if axis.Magnitude == 0 then
	axis = Vector3.new(1, 0, 0)
end

Model:SetPrimaryPartCFrame(CFrame.new(Pos + NewRay.Normal * Model.PrimaryPart.Size.Y / 2) * Frame.fromAxisAngle(axis, angle))

Didn’t realise Pos was a CFrame as my attention had been brought to the NewPos variable.

Model:SetPrimaryPartCFrame(CFrame.new(Pos.Position + NewRay.Normal * Model.PrimaryPart.Size.Y / 2) * Frame.fromAxisAngle(axis, angle))

Yes, it hasn’t been tested so there may be more errors

Oooh it’s so close :pleading_face: The only problem it faces is going on the ceiling.


On floor and walls it’s perfect, but ye it’s not rotating on the roof :confused:

I applied some edits:

local axis = Vector3.new(0, 1, 0):Cross(normal)
local angle = math.asin(axis.Magnitude)
axis = axis.Magnitude == 0 and Vector3.new(1, 0, 0) or axis

if axis.Magnitude == 0 then

    if normal.y < 0 then
        angle = math.pi
    end

    axis = Vector3.new(1, 0, 0)
end

Model:SetPrimaryPartCFrame(CFrame.new(pos + normal * Model.PrimaryPart.Size.Y / 2) * CFrame.fromAxisAngle(axis, angle))


Still didn’t rotate :confused:

yOffset: model.PrimaryPart.Size.Y/2
surfacePos: position from the raycastresult snapped to grid

local function calculateCf(oldCf, yOffset, surfacePos, normal)
	local oldUpVec = oldCf.UpVector
	local oldRot = oldCf-oldCf.Position
	local newPos = surfacePos+normal*yOffset
	local dot = oldUpVec:Dot(normal)
	if dot > 1-1e-5 then
		return oldRot+newPos
	end
	if dot < -1+1e-5 then
		return CFrame.Angles(math.pi, 0, 0)*oldRot+newPos
	end
	local scalarAngle = math.acos(dot)
	local rotationAxis = oldUpVec:Cross(normal).Unit
	return CFrame.fromAxisAngle(rotationAxis, scalarAngle)*oldRot+newPos
end
3 Likes

Not sure if I’m doing it right, but I get an error
invalid argument #2 (Vector3 expected, got CFrame)

Model:SetPrimaryPartCFrame(calculateCf(Model.PrimaryPart.CFrame, Model.PrimaryPart.Size.Y / 2, Pos, NewRay.Normal))

Is Pos a CFrame? The function needs a Vector. You could give its position to the function, if it’s a CFrame.

local NewPos = Vector3.new(PosX, PosY, PosZ)
		
	local Pos = (Model.PrimaryPart.CFrame - Model.PrimaryPart.Position) + NewPos

Actually, just give it the NewPos.

1 Like