Finding the angle of a wedgepart

Hi,

Does anyone have any idea on how to find the angle in a wedge part? More details below:

So what I am trying to do is find the angle of this green section here on a wedge:

Eventually the end goal is to be able to angle a part via script on a wedge just like this:

My first thought, was to try to use Pythagorean theorem:

function pythThem(part)
	local a = part.Size.Y
	local b = part.Size.Z
	
	return math.sqrt((a^2)+(b^2))
end

local c = pythThem(game.Workspace.Wedge)

This got me the size of the green part, but did not get me the angle like I wanted.

Does anyone have any idea how to do this?

3 Likes

For this you would have to use SOHCATOA

You can read about it here

1 Like

The answer to the question you asked is to use trigonometry:

local opposite = part.Size.y;
local adjacent = part.Size.z;
local angle = math.atan2(opposite, adjacent);

But how are you determining where to put the part? You may have an easier time using linear algebra.

4 Likes

How would I use linear algebra here?

My original idea was to use raycasting to get the point, then implement the rotation.

I recommend raycasting at the spot on the wedge that you’d like to attach the part to
This will give you the surface normal, which is important for cframing the part correctly

Example:

local Wedge = workspace.Wedge
local p2 = Wedge.Position + Wedge.CFrame.LookVector * Wedge.Size.Z/2
local ray = Ray.new(p2, (Wedge.Position - p2).unit * Wedge.Size.Z)

local part, hit, normal = workspace:FindPartOnRay(ray)

if part == Wedge then
	local brick = Instance.new("Part")
	brick.Anchored = true
	brick.CFrame = CFrame.new(hit + normal * brick.Size.Z/2, hit + normal * 10000)
	brick.Parent = workspace
end

Just tried this in a baseplate and it worked :wink:

Can you get the angle with FindPartOnRay?

Like here’s another example:

https://gyazo.com/028ce0f2e059bd56f8c8182054c638be

Can I make the part match the rotation of the wedgepart when I move the part?

Code I wrote to demo

local part2 = Instance.new('Part', workspace)
part2.Size = Vector3.new(1,1,1)
part2.BrickColor = BrickColor.Red()
part2.Transparency = 0.5
part2.Anchored = true
part2.CanCollide = false

local ignoreList = {}

table.insert(ignoreList, part2)

while wait() do

	local ray = Ray.new(workspace.Part.Position, Vector3.new(0, -100, 0))
	
	local part, hit, normal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
	
	
	part2.Position = hit + Vector3.new(0, 0, 0)
end
1 Like

The code I sent earlier matches the part to the wedge so that it fits (rotates)- you won’t need the angle

but
if you want the angle:

function angle(v1, v2)
    return math.acos(v1.unit:Dot(v2.unit))
end

print(math.deg(angle(normal, Wedge.CFrame.LookVector)))

You can achieve this by acquiring the surface normal of the green face which you’ve highlighted.
You can acquire this by firing a ray in the direction testDistance*Vector3.new(0, -1, 0).

Alternatively, if you know what wedge is being dealt with… You can acquire the surface normal by doing, (linear algebra soln)

local Zlen, Ylen = wedge.Size.Z, wedge.Size.Y
local rv, bv = wedge.CFrame.RightVector, -(Zlen*wedge.CFrame.LookVector - Ylen*wedge.CFrame.UpVector).Unit
local surfaceNormal = bv:Cross(rv)

From there, it looks like you want to position the part on the green face with the parts upvector / topFace in the same direction as the surface normal of the green face.

Get the x, z position of where you want the part to go and use them to find the y position.
And from there, all should be calculable.

local ySize = 4 --Size.y of part being placed on wedge
local x, z = 1, 2 --Position.x & Position.z of part being placed on wedge
local posn = wedge.CFrame:PointToWorldSpace(Vector3.new(0, 1, 1)*wedge.Size/2) + surfaceNormal*ySize/2
local x0, y0, z0 = posn.X, posn.Y, posn.Z
local y = -(surfaceNormal.x*(x - x0) + surfaceNormal.z*(z - z0))/surfaceNormal.y + y0
local newPosition = Vector3.new(x, y, z)

function PointLocalVectorAt(objectSpaceVector, originPosition, lookAtPosition)
	return CFrame.new(originPosition, lookAtPosition)*CFrame.new(Vector3.new(), objectSpaceVector):inverse()
end

part.CFrame = PointLocalVectorAt(Vector3.new(0, 1, 0), newPosition, newPosition + surfaceNormal)
1 Like

Only thing is that the code you sent earlier is using the Wedge to make the ray, for what I am actually going to be using this for I need to use the part for the ray though.

Eventually, the game will have over 50 wedges and I want all of the orientations to be able to line up.

The final piece of the puzzle is assembling the CFrame using look vectors. The normal is one of the lookVector. A sensible second lookVector is the x axis of the part you’re hitting.

I modified your code to get this result:

Code
local part2 = Instance.new('Part', workspace)
part2.Name = "Indicator";
part2.Size = Vector3.new(1,1,1)
part2.BrickColor = BrickColor.Red()
part2.Transparency = 0.5
part2.Anchored = true
part2.CanCollide = false

local ignoreList = {}

table.insert(ignoreList, part2)

while wait() do
	local ray = Ray.new(workspace.Part.Position, Vector3.new(0, -100, 0))
	local part, hit, normal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)

	--Here, we use the `normal` variable to indicate the y look vector for our indicator part.
	--The second lookVector we can assign to the x axis of the part we are scanning
	--(note: a part where the x axis points straight up will break this).
	local yLook = normal;
	local xLook = part.CFrame.rightVector;
	--Derive the z lookVector from the first two lookVectors.
	local zLook = xLook:Cross(yLook);
	part2.CFrame = CFrame.fromMatrix(hit, xLook, yLook, zLook);
end
3 Likes

Careful, part.CFrame.RightVector isn’t always guaranteed to be perpendicular to the normal vector. (edit: Was thinking of a corner wedge as one if it’s faces isn’t perpendicular to it’s rightface, sorry). (Though, I think Roblox auto-fixes the CFrame if the local axes don’t have orthogonality anyway.)

Also, it might be better if the part lies flat on the surface with the same x, z position. That code puts half of the part ontop, and half below the surface. If you wish to have the part lie “flat” on any surface at any exact x, z position, I would use a slightly modified version of my code above along with your code.

local part2 = Instance.new('Part', workspace)
part2.Name = "Indicator";
part2.Size = Vector3.new(1,1,1)
part2.BrickColor = BrickColor.Red()
part2.Transparency = 0.5
part2.Anchored = true
part2.CanCollide = false

local ignoreList = {}

table.insert(ignoreList, part2)

function PointLocalVectorAt(objectSpaceVector, originPosition, lookAtPosition)
	return CFrame.new(originPosition, lookAtPosition)*CFrame.new(Vector3.new(), objectSpaceVector):inverse()
end

local testDistance = 100

while wait() do
	local ray = Ray.new(workspace.Part.Position, Vector3.new(0, -1, 0)*testDistance)
	local part, hit, surfaceNormal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)

	local ySize = part2.Size.y
	local x, z = workspace.Part.Position.x, workspace.Part.Position.z --Position.x & Position.z of part being placed on wedge
	local posn = hit + surfaceNormal*ySize/2
	local x0, y0, z0 = posn.X, posn.Y, posn.Z
	local y = -(surfaceNormal.x*(x - x0) + surfaceNormal.z*(z - z0))/surfaceNormal.y + y0
	local newPosition = Vector3.new(x, y, z)

	part2.CFrame = PointLocalVectorAt(Vector3.new(0, 1, 0), newPosition, newPosition + surfaceNormal)
end

This should work with terrain too I believe, as well as any BasePart.

7 Likes

Yeah, I opted to remove all the corner cases in favor of having a solution that could be easily understood by OP. Plus, in case he’s only interested in solving this for the slanted portion of a wedge part, the solution is sufficient.

I only left a very subtle warning that my solution doesn’t capture every edge case. :slight_smile:

--(note: a part where the x axis points straight up will break this).

1 Like

Thank you so much everyone! :hearts:

1 Like