Raycast Surface Normal & Character LookVector

I am building a system to allow characters to lift crates/boxes and put them down. I am having issues with the releasing part of the problem.

I currently have it raycast downwards from the player to determine the raycast result position & normal to align the crate with. See this sample image for an example:
image

However, I want this only to occur on the X & Z axis. For the Y axis, that should be aligned with the character’s lookvector so that it looks like the box has been put down in front/facing him.

This is the current Raycast code:

local v3 = raycastResult.Position
local rX, _, rZ = CFrame.new(v3, v3 + raycastResult.Normal):ToEulerAnglesXYZ()
box:SetPrimaryPartCFrame(CFrame.new(v3) * CFrame.Angles(rX, 0, rZ))

Can’t seem to figure out that problem though. I tried:

local _, rY, _ = CFrame.new(v3, root.Position):ToEulerAnglesXYZ()

though that had incredibly weird & incorrect results.

Appreciate any support.

-G

1 Like

output

We can completely define an orientation with three orthogonal vectors, the RightVector, UpVector and LookVector.

Your 1st constraint is that the UpVector must be parallel to the surface the box is placed on, so that’s the UpVector defined (red arrow).

The 2nd constraint is that the LookVector should be as close as possble to the LookVector of the character (green arrow), within the 1st constraint. This is not guaranteed to be the same as the LookVector of the character, e.g. any situation where the surface is not perfectly horizonta,l so it wouldn’t be orthogonal to the red arrow.

image

We can figure out what the RightVector (purple arrow) should be. It is the vector that is orthogonal to both the UpVector / surface normal (red) and the LookVector of the character (green). This is the cross product of these two vectors, so

rightVector = surfaceNormal:Cross(character.CFrame.LookVector)

There is a CFrame constructor that takes the RightVector, UpVector and optionally the LookVector to construct a CFrame:

CFrame.fromMatrix ( Vector3 pos, Vector3 vX, Vector3 vY, Vector3 vZ )
Creates a CFrame from a translation and the columns of a rotation matrix. If vz is excluded, the third column is calculated as [vx:Cross(vy).Unit].

We’ll omit the optional LookVector, which lets the constructor automatically figure out what it should be:

box.Position = --the position it shouldl be at
box.CFrame = CFrame.fromMatrix(box.Position, surfaceNormal, surfaceNormal:Cross(character.CFrame.LookVector)

Swap the order of arguments to get the exact rotation you want, or just rotate by a constant to get it right :stuck_out_tongue:

Here’s a test place showing how it can be implemented

PlaceOnSurface.rbxl (31.0 KB)

Let me know if you have any questions or if anything is unclear :slight_smile:

5 Likes

Amazing work, and thank you for the demo implementation!

One question, I noticed you are using the cam’s LookVector. I tried implementing the HRT lookvector but it acts strange. Any idea to the reason?

1 Like

HRT? Is that HumanoidRootPart? Dunno, seems to me that it should work fine.

Here's a version that places the box in front of the character:
local rayO = game.Workspace.Origin
local box = game.Workspace.Box
local cam = game.Workspace.CurrentCamera
local char = game.Players.LocalPlayer.Character or game.Players.LocalPlayer.CharacterAdded:Wait()

while wait() do
	local rootPart = char.PrimaryPart
	local p = RaycastParams.new()
	p.FilterType = Enum.RaycastFilterType.Blacklist
	p.FilterDescendantsInstances = {box}
	local r = game.Workspace:Raycast(rootPart.Position + rootPart.CFrame.LookVector * 4, Vector3.new(0, -1000, 0), p)

	if r then
		local pos = r.Position
		local n = r.Normal
		local boxPos = pos + n * box.Size.Y/2
		
		box.CFrame = CFrame.fromMatrix(
			boxPos, 
			n, 
			n:Cross(rootPart.CFrame.LookVector)
		) * CFrame.Angles(0, 0, math.pi/2)
	end
end

PlaceOnSurfaceInFront.rbxl (31.1 KB)

1 Like