Seems like you might be better off adding a VectorForce to each corner of the part depending on how deep under the surface you are.
But since you didn’t ask about that, I’ll give your question a go.
I understand what you mean, but its a bit hard to quantify this.
Like it seems like a simple problem at first—project the RightVector and LookVector onto the plane, and use the UpVector as the plane normal.
However, when you project the Right/Look vectors onto the plane, they won’t usually be 90 degrees apart anymore:
My solution for now is just to decide to align the LookVector and set the RightVector accordingly:
That seems to work fine for visuals at least. There’s probably some more sophistocated solution where you find the minimum rotation required to have the part lie in the plane… but that is beyond me at the moment.
Final code:
local part = workspace.Part
local a, b, c = workspace.A, workspace.B, workspace.C
local proj = part:Clone()
proj.BrickColor = BrickColor.Red()
proj.Parent = workspace
local plane = Instance.new("Part")
plane.Size = Vector3.new(20, 0.1, 20)
plane.Anchored = true
plane.Transparency = 0.5
plane.BrickColor = BrickColor.Blue()
plane.Parent = workspace
-- computes some CFrame on the plane formed by a, b, and c
-- note: only used to orient the blue plane
local function PlaneCFrame(a, b, c)
local ab, ac = b - a, c - a
local up = ab:Cross(ac)
if up.Y < 0 then up = -up end
return CFrame.fromMatrix((a + b + c) / 3, ab, up)
end
-- projects a point vertically onto the plane
-- vec: point to project
-- p: point on the plane
-- n: unit normal of plane
local function ProjectVertically(vec, p, n)
local off = vec - p
local y = -(n.X*off.X + n.Z*off.Z)/n.Y
return p + Vector3.new(off.X, y, off.Z)
end
-- projects cf vertically onto the plane formed by a, b, and c
-- such that cf.LookVector does not change rotation around
-- the y axis and that cf.RightVector lies in the plane
local function ProjectToPlane(cf, a, b, c)
local ab, bc = b - a, c - b
local n = ab:Cross(bc).Unit -- normal vector of plane
if n.Y < 0 then n = -n end -- points up
-- project lookVector to plane vertically:
local look = cf.LookVector
look = look - look:Dot(n)*n
local right = look:Cross(n)
-- optional: project position on plane straight down
local pos = ProjectVertically(cf.Position, a, n)
return CFrame.fromMatrix(pos, right, n, -look)
end
game:GetService("RunService").Stepped:Connect(function()
proj.CFrame = ProjectToPlane(part.CFrame, a.Position, b.Position, c.Position)
plane.CFrame = PlaneCFrame(a.Position, b.Position, c.Position)
end)