Raycast Using a Blacklist AND a Whitelist Simultaneously

As of late I’ve found myself jumping through hoops to get the Raycast results I’m looking for.

Usually this is because I have parts within the same model/folder that I would like some rays to ignore, and some to hit. I can’t make much use of CanQuery because as mentioned, I’d like the parts to be detectable some of the time.

It’s also not efficient to be separating these objects because my builders need to be able to move them around easily.

A new addition to ray casting that would allow me to cast a single ray with BOTH a blacklist, and a whitelist simultaneously would be exceptionally helpful for this use-case.

For example:

local Characters = {}

for _,Player in (game.Players:GetPlayers()) do
 if Player.Character then
  table.insert(Characters,Player.Character)
 end
end

local Whitelist = Characters
local Blacklist = {game.Players.LocalPlayer.Character}

local RayParams = RaycastParams.new()
RayParams.FilterType = Enum.RaycastFilterType.Greylist
RayParams.FilterDescendantsInstances = {Whitelist,Blacklist}

9 Likes

It sounds like you’re trying to achieve a selective raycasting behavior that is not currently possible with the existing RaycastParams options. However, there are a few workarounds you can try to achieve the desired effect.

One possible solution is to perform multiple raycasts with different filters and combine their results. For example, you could perform one raycast with the whitelist and another with the blacklist, and then only consider hits that are present in the whitelist but not in the blacklist.

Here’s an example implementation:

local Characters = {}

for _, Player in pairs(game.Players:GetPlayers()) do
    if Player.Character then
        table.insert(Characters, Player.Character)
    end
end

local Whitelist = Characters
local Blacklist = {game.Players.LocalPlayer.Character}

local WhitelistHits = workspace:Raycast(game:GetService("UserInputService").MouseBehavior.Position, workspace.CurrentCamera.CFrame.LookVector * 100, RaycastParams.new().FilterDescendantsInstances = Whitelist)
local BlacklistHits = workspace:Raycast(game:GetService("UserInputService").MouseBehavior.Position, workspace.CurrentCamera.CFrame.LookVector * 100, RaycastParams.new().FilterDescendantsInstances = Blacklist)

local Hits = {}

if WhitelistHits then
    for _, Hit in pairs(WhitelistHits) do
        if not table.find(BlacklistHits, Hit) then
            table.insert(Hits, Hit)
        end
    end
end

-- Process the hits as needed

This code performs two separate raycasts, one with the whitelist and one with the blacklist, and then combines their results into a new table of hits that are present in the whitelist but not in the blacklist.

Another possible solution is to use a custom filtering function instead of the RaycastParams options. This would allow you to implement any kind of filtering logic you need, including both whitelist and blacklist behavior.

Here’s an example implementation:

local Characters = {}

for _, Player in pairs(game.Players:GetPlayers()) do
    if Player.Character then
        table.insert(Characters, Player.Character)
    end
end

local Whitelist = Characters
local Blacklist = {game.Players.LocalPlayer.Character}

local function CustomFilterFunction(Part)
    if table.find(Whitelist, Part) then
        return true
    end
    
    if table.find(Blacklist, Part) then
        return false
    end
    
    -- Add any other filtering logic as needed
    
    return true
end

local Hit = workspace:FindPartOnRayWithIgnoreList(Ray.new(game:GetService("UserInputService").MouseBehavior.Position, workspace.CurrentCamera.CFrame.LookVector * 100), Blacklist, false, true, CustomFilterFunction)

-- Process the hit as needed

This code uses the FindPartOnRayWithIgnoreList function instead of the Raycast function, and passes a custom filtering function as the last argument. This function checks if the part is in the whitelist or the blacklist, and returns true or false accordingly. You can add any other filtering logic you need to this function as well.

I hope these suggestions help you achieve the raycasting behavior you need.

I’m well aware of this and already do something similar. The issue of course is that raycasts are quite expensive, as can be this entire custom filtering system.

An operation on Roblox’s backend that handles it more efficiently would be greatly appreciated.

5 Likes

Thanks for making a topic, I’ve been waiting to talk about this.

My opinion is why even have a FilterType to begin with? Just add 2 seperate properties that aren’t mutually exclusive to a mode, like:

RaycastParams.IncludeDescendants = {BasePart}
RaycastParams.ExcludeDescendants = {BasePart}

And if we’re on the topic of ergonomics, allow these properties to also be single BaseParts, because I don’t need a dedicated table just to exclude the character.

11 Likes

Could you not just do this?

local Whitelist = {}
local Blacklist = {}

Loop through your blacklist and if an instance of it exists in whitelist then remove it

Raycast with just your edited whitelist table

When you’re using simple tables like this the use-case is pretty hard to see. It really matters though when you’re talking about descendants of the world model.

Imagine you had some folders in workspace.

workspace.Buildings
workspace.Props
workspace.Foliage

You want a raycast that will only hit things inside workspace.Buildings

Obviously, that’s a whitelist raycast using {workspace.Buildings} as the Whitelist.

Now imagine you had a couple buildings within workspace.Buildings that you wanted to ignore. Like “BuildingA” for example.

Currently you’d need to do:

local Whitelist = workspace.Buildings:GetChildren()
local Index = table.find(Whitelist, workspace.Buildings.BuildingA)
if Index then
 table.remove(Whitelist, Index)
end

or even WORSE if you wanted to ignore only a specific part of BuildingA:

local Whitelist = workspace.Buildings:GetDescendants()
local Index = table.find(Whitelist, workspace.Buildings.BuildingA.SpecificPart)
if Index then
 table.remove(Whitelist, Index)
end

This makes the terms confusing.

Whitelist → only these items.
Blacklist → all items but these can exist
Whitelist & Blacklist → only 3 items can exist and 3 cannot (although they already cannot since they are not a part of the whitelist)

I’m not too experienced in raycasting, but doesn’t this just make it redundant?

No it’d be a blacklist of the whitelist

1 Like

Couldn’t collection service work here? I guess it depends on how many instances you want in these lists and how deeply nested they are. Having this as a feature would be useful but I’m sure this could be a decent workaround for now.

I don’t see how this would be useful to many people.

With the code snippet provided above, you don’t even need a blacklist. All you need is an extra check before insertion into the Characters array:

local Characters = {}

for _, Player in (game.Players:GetPlayers()) do
    if
        Player.Character
        and Player ~= game.Players.LocalPlayer --> just check if it's not the local player
    then
        table.insert(Characters,Player.Character)
    end
end

It’s also more logically sound with the current system. For example, if you’re using the Greylist, are you blacklisting parts in the whitelist, or whitelisting parts in the blacklist?

4 Likes

You should be able to set the collision group on the RaycastParams to get the behavior you want.

In that past this was annoying to manage when collision groups were based on Ids but now that they’re based on string identifiers it should be relatively convenient to configure what hits what, including raycasts, using collision groups.

Specifically what you want to do is make one or more dedicated “raycast” collision groups so that you can choose exactly what categories of thing get hit.

CollisionGroups are a bad design for Raycast filtering IMO because they are very singular and affect more than just what I want to do (What if I had TouchesUseCollisionGroups enabled?)

Most of my use cases revolve around just filtering through a general list of characters/ walls/whatever and excluding one or two of them because I want them ignored.

For example, I would much rather do this:

local Params = RaycastParams.new()
Params.IncludeDescendants = GetCharacters()
Params.ExcludeDescendants = Player.Character

Then do something like this:

local Params = RaycastParams.new()
	
local Characters = GetCharacters()
for Index, Character in Characters do
	if Character == Player.Character then
		Characters[Index] = nil
	end
end

Params.FilterDescendantsInstances = Characters
1 Like

Instead of providing the ancestor, create an array of descendants for the Whitelist which takes the Blacklist array into consideration. This way you will get more control over what it is you wish to Raycast on.

This isn’t dynamic enough. I’m looking for a way to achieve a blacklist of the whitelist type behavior without having to index through thousands of descendants.

Using collision groups still requires an equal amount of indexing through the whitelist before casting.

I’m not really looking for just control, that’s easy to achieve with arrays like you mentioned. I’m looking for that level of control without having to index through hundreds or even thousands of descendants before the cast.

Roblox would have to do the same thing internally with what you are asking for, so until that feature exists you would have to make your own version and take the slight performance hit, not that Luau is a slow language anyways.

1 Like