I have written a script that will compute the intersection point between a line segment and a plane. The algorithm to do this is quite simple and can be found easily anywhere on the internet.
I would like to use this with a plane facing in any direction that passes through two points on the edge of a box and find where it intersects with a line segment. An example of this is shown below (the plane passes through the two green points on the box).
The problem: Having found an intersection point, it should be possible to do a raycast back along the plane from the intersection point, and return a point on the edge of the box.
What happens in reality is the raycast just misses the edge of the box. The purple line is a visual representation of the raycast.
Since I found an intersection point between the plane and the line segment this SHOULD not be possible. I’m guessing this is some sort of floating point error, or a byproduct of dealing with such precise values, but what is the solution?
Below is a place with all the code and setup I am using to test this. Move the red parts to alter the line segment and the yellow part to change the projection of the plane. Just click ‘Run’ instead of ‘Play’.
planeIntersectionTesting.rbxl (20.6 KB)
I will include the code here as well.
local SMALL_NUM = 0.0001
-- Returns the normal of a plane from three points on the plane
-- Inputs: Three vectors of points on the plane.
function equation_plane(vec1, vec2, vec3)
local diff1 = vec2 - vec1
local diff2 = vec3-vec1
return diff1:Cross(diff2)
end
-- Create a point at the specified vector
function createVisualPoint(position)
local visualPoint = Instance.new("Part")
visualPoint.Name = "visualPoint"
visualPoint.Anchored = true
visualPoint.BrickColor = BrickColor.new("Lime green")
visualPoint.Size = Vector3.new(0.2,0.2,0.2)
visualPoint.CFrame = CFrame.new(position)
visualPoint.Parent = workspace
return visualPoint
end
-- Create an edge in space between point1 and point2
function createVisualEdge(point1, point2)
local visualEdge = Instance.new("Part")
visualEdge.Name = "visualEdge"
visualEdge.Anchored = true
visualEdge.BrickColor = BrickColor.new("Neon orange")
local size = (point1-point2).magnitude
visualEdge.Size = Vector3.new(0.1,0.1,size)
visualEdge.CFrame = CFrame.new(point1, point2) * CFrame.new(0,0,-size/2)
visualEdge.Parent = workspace
return visualEdge
end
-- Create a visual plane defined by a vector3 point and normal.
function createVisualPlane(point, normal)
local visualPlane = Instance.new("Part")
visualPlane.Name = "visualPlane"
visualPlane.Anchored = true
visualPlane.Size = Vector3.new(20,20,0.1)
visualPlane.CFrame = CFrame.new(point, point+normal)
visualPlane.Transparency = 0.2
visualPlane.Parent = workspace
return visualPlane
end
-- Returns the intersection point of a line segment and a plane
-- Inputs
-- SP0 (vector3): A point on the line segment
-- SP1 (vector3): A point on the line segment
-- planePoint (vector3): A point on the plane
-- planeNormal (vector3): The normal vector to the plane
function intersectSegmentPlane(SP0, SP1, planePoint, planeNormal)
local u = SP1 - SP0;
local w = SP0 - planePoint;
local D = planeNormal:Dot(u);
local N = -(planeNormal:Dot(w))
if (math.abs(D) < SMALL_NUM) then -- segment is parallel to plane
if (N == 0) then -- segment lies in plane
return SP0;
else
return nil; -- no intersection
end
end
-- they are not parallel
-- compute intersect param
local sI = N / D;
if (sI < 0 or sI > 1) then
return nil; -- no intersection
end
local I = SP0 + sI * u; -- compute segment intersect point
return I;
end
local visualEdge
local visualPlane
local visualRaycast
local visualPoint1
local visualPoint2
game:GetService("RunService").Heartbeat:Connect(function()
-- Cleanup visuals
if visualEdge then visualEdge:Destroy() end
if visualPlane then visualPlane:Destroy() end
if visualRaycast then visualRaycast:Destroy() end
if visualPoint1 then visualPoint1:Destroy() end
if visualPoint2 then visualPoint2:Destroy() end
local box = workspace.box
-- Get the projection from planeProjection in workspace
local planeProjection = workspace.planeProjection.Position
-- Set two points on the plane to an edge on the box.
local planePoint1 = (box.CFrame * CFrame.new(box.Size.x/2, box.Size.y/2, -box.Size.z/2)).p
local planePoint2 = (box.CFrame * CFrame.new(box.Size.x/2, -box.Size.y/2, -box.Size.z/2)).p
-- Set point 3 to the projection of the plane from planePoint2
local planePoint3 = planePoint2 + planeProjection
-- Define the two points that will make up the line segment
local lineSegmentPoint1 = workspace.lineSegmentPoint1.Position
local lineSegmentPoint2 = workspace.lineSegmentPoint2.Position
-- Get the normal to the plane from the 3 points on the plane
local normal = equation_plane(planePoint1, planePoint2, planePoint3)
-- Get the intersetion point between the plane and the line segment
local intersection = intersectSegmentPlane(lineSegmentPoint1, lineSegmentPoint2, planePoint1, normal)
if intersection then
-- Raycast back from the intersection in the opposite direction of the plane's projection.
-- This raycast SHOULD always return a hit because a hit was found between the line segment and the plane.
local ray = Ray.new(intersection, -planeProjection)
local part, position = workspace:FindPartOnRayWithWhitelist(ray, {workspace.box})
-- Visually display the raycast (it will be evident that the raycast misses the box, WHYYY??)
visualRaycast = createVisualEdge(position, intersection)
visualRaycast.BrickColor = BrickColor.new("Hot pink")
end
-- Create visuals.
visualPoint1 = createVisualPoint(planePoint1)
visualPoint2 = createVisualPoint(planePoint2)
visualEdge = createVisualEdge(lineSegmentPoint1, lineSegmentPoint2)
visualPlane = createVisualPlane(planePoint1, normal)
end)