*This method needs manifold geometry (closed and with outward normals)*
With the Raycast node and a few chained raycasts, you can get a good result:
- Red: Inside Points
- Yellow: Unsure Points
- Black: Outside Points
But for a lot of instances intersecting the number of iterations needed grows:
I have some other ideas for optimizing for very clumped instances, I may test them and update the answer later.
The idea is simple, first for a point raycast to the source surface using the source normal, if the raycast hits and the hit normal points away from the source normal, then the point is inside a mesh.
To know if the hit normal points away or towards the point normal we can use the Dot Product operation, it receives two vectors and outputs the cosine of the angle between them if they are normalized, so if the result is greater than $0$ the hit comes from inside, else it comes from outside.
A single raycast will work if there's a maximum of one intersection per mesh, but, if there isn't, more raycasts starting from the previous raycast hit position will be needed, and the result will depend on the number of times a ray enter and exit a mesh, if it exits more times than it enters, it is inside some geometry.
For a point, first store an integer to be used as a depth counter, then raycast to the source surface, if there's no hit, it's outside; if the hit normal is opposite to the point normal, subtract $1$ from depth, else add $1$. As soon as depth becomes 1, you know the ray started [and still is] inside some geometry, but while it doesn't hit, repeat the raycast process, accumulating depth until it reaches $1$ or does not hit.
A way to reduce the number of iterations needed for some points is also raycasting backwards, storing another integer for the backwards depth, but this one starts at $-1$, since the ray will have exit the source mesh, adding an extra $1$ to the total.
In Geometry nodes, first we prepare our variables:
- direction: the point normal, used as the raycast direction.
- hit_pos: the position hit by the last forward raycast, initialized as the point position.
- hit_depth: the depth of the forward raycast, starting at 0.
- inv_hit_pos: the position hit by the last backward raycast, initialized as the point position.
- inv_depth: the depth of the backward raycast, starting at -1, since the back of the source mesh will eventually be hit, adding an extra $1$ to the value.
Now we need a node group that does the raycasting and can be chained:
Something important to do is offset the raycast source in the raycast direction by a very small amount, if that's not done, the raycast can hit the place where the point was distributed. The offset value in the blend file I provided is $0.001$, but can be made smaller inside the node group if your meshes are of smaller scale, I tested until $0.00005$.
At the end of the chain, we can remove the named attributes used for calculation:
Here there's switches that output no geometry if the source is empty, the only purpose of that is to stop the 'named attribute not found' warning. I also added an option to join unsure points to the resulting points (which contains the outside points).
In blender 4.0, with repeat zones, you won't need to duplicate the chain nodes anymore, the number of iterations can be chosen in the node: