Could you perhaps explain how to do the third method? Sorry, again I’m not the best at scripting (especially raycasting.)
1 Like
Something like this should work, thought I didn’t test it thoroughly:
local function RaycastWithACondition(condition, origin, direction, raycastParams) -- condition is a function which returns true or false
local raycastResult = workspace:Raycast(origin, direction, raycastParams)
if raycastResult == nil or condition(raycastResult.Instance) then -- this is to ensure we dont call condition(nil)
return raycastResult -- returns nil or RaycastResult
end
-- if the condition didnt pass
local intersection = raycastResult.Position
direction = direction - (intersection - origin) -- previous direction reduced by the distance traveled from the previous origin to the intersection
origin = intersection -- the intersection is the new origin of the next ray
return RaycastWithACondition(condition, origin, direction, raycastParams) -- recursion, back to the top of the function with new origin and direction
end
I implemented the above one with recursion, you can also use a while loop:
local function RaycastWithACondition(condition, origin, direction, raycastParams) -- condition is a function which returns true or false
while true do
local raycastResult = workspace:Raycast(origin, direction, raycastParams)
if raycastResult == nil or condition(raycastResult.Instance) then -- this is to ensure we dont call condition(nil)
return raycastResult -- returns nil or RaycastResult
end
-- if the condition didnt pass
local intersection = raycastResult.Position
direction = direction - (intersection - origin) -- previous direction reduced by the distance traveled from the previous origin to the intersection
origin = intersection -- the intersection is the new origin of the next ray
end
end
You should probably put this in some module script and return it (or put it in a table and return the table), so any script can access it.
Here is an example of the condition function:
local function condition(instance)
return (instance.Transparency < 1 and instance.CanCollide) -- true if a part's Transparency is below 1 and CanCollide is true, otherwise false
end
Some implementation notes:
- I tested it and it seems like if you cast a ray that originates from the intersection of the previous ray and a part, it won’t hit the same part again. Basically, a ray that starts from inside a part and goes through it, doesn’t intersect with it. This might not be true for some kinds of unions/meshes with complicated geometries, but the function should still work. This way it’s simpler and we don’t have to mess with tables/Blacklists. From what I know, RaycastParams.FilterDescendantsInstances copies any table you set it to, so it would essentially be creating a new table every time, which wouldn’t be good.
- I also tested for the edge case of the ray hitting a part which doesn’t pass the condition at exactly origin + direction. I couldn’t get it to happen. In that case, it would attempt a useless raycast with a direction vector of Vector3.new(0, 0, 0). It would still correctly return a nil, but we could technically prevent the extra workspace.Raycast call by checking if direction == Vector3.new() or if direction.Magnitude == 0. I found this check unnecessary, since it seems like rays don’t detect an intersection at the end of the ray. I tested this by casting a ray with an origin of Vector3.new(0, 0, 0) and a direction of Vector3.new(0, 1, 0). It intersected a part above it at Vector3.new(0, 0.5, 0). Then I tested the same scenario with a direction of Vector3.new(0, 0.5, 0) and there was no intersection.
4 Likes
Just coming back I would use a while loop because of callstack overflow.
Hey people, forgot to put a solution on this. Roblox now has a new property called ‘CanQuery’ which manages this exact thing. Therefore, this thread is now deprecated. Sorry everyone!
https://developer.roblox.com/en-us/api-reference/property/BasePart/CanQuery
3 Likes