Community
3ds Max Programming
Welcome to Autodesk’s 3ds Max Forums. Share your knowledge, ask questions, and explore popular 3ds Max SDK, Maxscript and Python topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

How to check if a point is inside a closed mesh?

18 REPLIES 18
Reply
Message 1 of 19
Anonymous
2001 Views, 18 Replies

How to check if a point is inside a closed mesh?

Hi everyone,

 

Is there a way to determine if a given 3d point lies inside a closed convex mesh? Thank you!

Tags (3)
Labels (3)
18 REPLIES 18
Message 2 of 19
denisT.MaxDoctor
in reply to: Anonymous

the solution is very simple and straightforward ... if you cast a ray from this point in any direction and:
#1 ray doesn't intersect mesh faces - OUTSIDE
#2 ray intersects but the first hit face's normal is opposite to ray direction - OUTSIDE
#3 ray intersects and the first hit face's normal has the same direction of the ray - INSIDE

Message 3 of 19


@denisT.MaxDoctor wrote:

the solution is very simple and straightforward ... if you cast a ray from this point in any direction and:
#1 ray doesn't intersect mesh faces - OUTSIDE
#2 ray intersects but the first hit face's normal is opposite to ray direction - OUTSIDE
#3 ray intersects and the first hit face's normal has the same direction of the ray - INSIDE


if the mesh is closed but not convex there is another rule...
(still cast a ray from the point to any direction):
#1 no hit - OUSIDE
#2 even number of hits - OUTSIDE
#3 odd number of hits - INSIDE

 

 

Message 4 of 19
miauuuu
in reply to: Anonymous

You can use the code in post 6 in this thread: https://forums.cgsociety.org/t/find-verts-inside-other-mesh/1456824/6

and following denisT advices you can build your tool.

https://miauu-maxscript.com/
Message 5 of 19
denisT.MaxDoctor
in reply to: miauuuu


@miauuuu wrote:

You can use the code in post 6 in this thread


today is 2021 ... let's do something smarter:

fn isPointInsideGeometry node pt = 
(
	rm = RayMeshGridIntersect() 
	rm.Initialize 10 
	rm.addnode node
	rm.buildGrid()

	numhits = rm.intersectRay pt z_axis on
	hitpoints = #() 
	for index = 1 to numhits do appendifunique hitpoints (rm.getHitPoint index)  
	
	res = mod hitpoints.count 2 == 1
	rm.free()
	res
)

fn arePointsInsideGeometry node pts = 
(
	rm = RayMeshGridIntersect() 
	rm.Initialize 10 
	rm.addnode node
	rm.buildGrid()

	res = for pt in pts collect
	(
		numhits = rm.intersectRay pt z_axis on
		hitpoints = #() 
		for index = 1 to numhits do appendifunique hitpoints (rm.getHitPoint index)  
		mod hitpoints.count 2 == 1
	)
	rm.free()
	res
)

 

🙂

 

Message 6 of 19
istan
in reply to: denisT.MaxDoctor


@denisT.MaxDoctor wrote:


if the mesh is closed but not convex there is another rule...
(still cast a ray from the point to any direction):
#1 no hit - OUSIDE
#2 even number of hits - OUTSIDE
#3 odd number of hits - INSIDE

 

 


As you wrote about "not convex". Concave objects won't work with a test ray in any direction..

Message 7 of 19
denisT.MaxDoctor
in reply to: istan


@istan wrote:

As you wrote about "not convex". Concave objects won't work with a test ray in any direction..


why not? the odd / even solution works for any "closed" mesh (both convex and concave). it must be not a self-intersecting mesh, a single-element or its elements must not overlap.

 

Message 8 of 19


@denisT.MaxDoctor wrote:

@istan wrote:

As you wrote about "not convex". Concave objects won't work with a test ray in any direction..


why not? the odd / even solution works for any "closed" mesh (both convex and concave). it must be not a self-intersecting mesh, a single-element or its elements must not overlap.

 


fn arePointsInsideGeometry node pts = 
(
	rm = RayMeshGridIntersect() 
	rm.Initialize 10 
	rm.addnode node
	rm.buildGrid()

	res = for pt in pts collect
	(
		numhits = rm.intersectRay pt z_axis on
		hitpoints = #() 
		for index = 1 to numhits do appendifunique hitpoints (rm.getHitPoint index)  
		mod hitpoints.count 2 == 1
	)
	rm.free()
	res
)

delete objects
gc()
	
t = torus_Knot radius:20 radius2:6 wirecolor:green
pp = for k=1 to 1000 collect 
(
	point pos:(random t.min t.max) size:5 wirecolor:brown
)
pts = for p in pp collect p.pos 
cc = arePointsInsideGeometry t pts
for k=1 to pp.count do pp[k].wirecolor = if cc[k] then red else gray  
Message 9 of 19
Anonymous
in reply to: Anonymous

Thank you all guys! I solved my problem by following the example:

3ds Max 2016 Help: RayMeshGridIntersect : ReferenceTarget (autodesk.com)

I needed it for a very basic application and that works well. 

@denisT.MaxDoctor I'll look at your script too, maybe I can optimize what I wrote. Thanks 🙂

Message 10 of 19
denisT.MaxDoctor
in reply to: Anonymous


@Anonymous wrote:

Thank you all guys! I solved my problem by following the example...


in that case, please show your solution

Message 11 of 19

Hey @denisT.MaxDoctor 

thanks for sharing, found this quite useful - just a short note that your code seems not to work for a teapot.

Martin Breidt

http://scripts.breidt.net

EESignature

→ please 'Like' posts that are helpful; if a post answers your question please click the "Accept Solution" button.
Message 12 of 19


@MartinBeh wrote:

Hey @denisT.MaxDoctor 

thanks for sharing, found this quite useful - just a short note that your code seems not to work for a teapot.


A teapot geometry is not a "closed" mesh. As I remember the solution works for a "closed" mesh only. You can "cap" the teapot and try the solution again.

Message 13 of 19
MartinBeh
in reply to: Anonymous

I did try adding a Cap modifier but still getting some points outside the teapot reported as "inside", e.g. below the spout.

 

Have not tried to debug it yet, just wanted to let you know.

 

Teapot is special anyway...

Martin Breidt

http://scripts.breidt.net

EESignature

→ please 'Like' posts that are helpful; if a post answers your question please click the "Accept Solution" button.
Message 14 of 19

try to increase the ray-to-mesh intersection grid size to 1000 and number segments for the teapot. It has to work with the example below:

 

fn arePointsInsideGeometry node pts = 
(
	rm = RayMeshGridIntersect() 
	rm.Initialize 1000 
	rm.addnode node
	rm.buildGrid()

	res = for pt in pts collect
	(
		numhits = rm.intersectRay pt z_axis on
		hitpoints = #() 
		for index = 1 to numhits do appendifunique hitpoints (rm.getHitPoint index)  
		hitpoints.count > 0 and (mod hitpoints.count 2 == 1)
	)
	rm.free()
	res
)

delete objects
gc()
	
t = teapot radius:20 segs:100 wirecolor:green
addmodifier t (cap_holes())
	
pp = for k=1 to 1000 collect 
(
	point pos:(random t.min t.max) size:2 wirecolor:gray
)
pts = for p in pp collect p.pos 
cc = arePointsInsideGeometry t pts
xp = for k=1 to pp.count collect 
(
	if cc[k] then 
	(
		pp[k].wirecolor = red
		dontCollect
	)
	else pp[k]
)
-- select xp

 

 

The algorithmic logic is correct, but the built-in RayMeshGridIntersect methods are probably buggy (not working correctly) for some values.

 

Message 15 of 19


@denisT.MaxDoctor wrote:

try to increase the ray-to-mesh intersection grid size to 1000 and number segments for the teapot.


Makes sense! If RayMeshGridIntersect does not work precisely, the rest cannot do a good job, either.

 

Unfortunately, adding a Cap_Holes modifier and increasing the grid size to 1000 still generates a few false positives. In fact, there is no difference between 10 and 1000 (aside from calculation times...).

 

FWIW: I am testing with code that generates the test points in a regular grid within the bounding box, not random points.

Martin Breidt

http://scripts.breidt.net

EESignature

→ please 'Like' posts that are helpful; if a post answers your question please click the "Accept Solution" button.
Message 16 of 19

We can do it faster with a smaller grid but test all three directions. (or more than three, the rule is the same - odd number of intersections)

 

fn arePointsInsideGeometry node pts = 
(
	fn _odd x = (mod x 2 > 0) 
	
	rm = RayMeshGridIntersect() 
	rm.Initialize 100 
	rm.addnode node
	rm.buildGrid()

	res = for pt in pts collect
	(
		x = rm.intersectRay pt x_axis on
		y = rm.intersectRay pt y_axis on
		z = rm.intersectRay pt z_axis on
			
		_odd z and _odd y and _odd z   	
	)
	rm.free()
	res
)


delete objects
gc()
seed 100	
t = teapot radius:20 segs:10 wirecolor:green
	
pp = for k=1 to 1000 collect 
(
	point pos:(random t.min t.max) size:2 wirecolor:gray
)
pts = for p in pp collect p.pos 
cc = arePointsInsideGeometry t pts
xp = for k=1 to pp.count collect 
(
	if cc[k] then 
	(
		pp[k].wirecolor = red
		dontCollect
	)
	else pp[k]
)
Message 17 of 19

there are mistakes anyway... well, add negative directions

Message 18 of 19

Adding a cap_holes() and also testing for negative directions still leaves some questionable points:

Here is a simple fail case:

arePointsInsideGeometry t #([3,13,24], [0,13,24])
#(true, true)

Not 100% certain though - maybe those points are exactly on the teapot surface...?

Martin Breidt

http://scripts.breidt.net

EESignature

→ please 'Like' posts that are helpful; if a post answers your question please click the "Accept Solution" button.
Message 19 of 19

The main issue in your case appears to be algorithmic.

The "method of parity of intersections" only works for a single closed figure, regardless of whether it is convex or not.

 

In the classic case of the teapot, we are dealing with several elements, and they are not closed. If we apply the Cap modifier, we can close the shapes (elements), but since there are still multiple elements, this contradicts the algorithm's conditions. Therefore, to determine a point's position relative to the entire teapot (inside or outside) we must check for mutual intersections among all elements.

 

One possible solution could be to use a Boolean (Union) operation on all elements. However, this may be a computationally heavy operation in general.

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report