Super-super fast Point Inclusion test for Cylinder/Breps?

You are very very welcome to the party! :sunglasses:

I will have to look up what you mean with the ā€œprojection parameterā€. If that can be used on a tilted cylinder, then that sounds like… we have a party! :slight_smile:

// Rolf

Similarly, inclusion testing for other primitives such as spheres and tori is also very trivial and very fast. Axis-oriented boxes are easy, custom aligned boxes a bit trickier as it involves a Transform.

The next step up would be to test inclusion for convex shapes consisting purely of planar boundary surfaces. You can convert these shapes to arrays of Planes, and the signed distance from a point to each plane must be either positive or negative (depending on how you orient your planes) for the point to be inside. Points that are randomly outside this shape can on average be rejected after testing less than half the boundary planes.

Yes, we are discussing the Transform part with @emilio. Once I have learned to manage the Transforms it should be fairly easy to get speedy tests for the Inclusion part.

// Rolf

Rhino.Geometry.Line.ClosestParameter() basically. And yes, orientation is irrelevant.

Ah … I was thinking to regular Rhino commands / user interface.
(That’s where I keep forgetting where things are … :wink: )
… Had not understood that you were talking about programming.

Thanks for the info ! :slight_smile:
… I did not know / remember about that property either … :worried:

@emilio ā€œThe 50 ms were for Geometry.Point objects … but different objects give different results.ā€

I tried the following transform code (PlaneToPlane) attempting to ā€œtilt backā€ the mesh and the cylinder to the word XY plane, but it goes on forever in the transform loop, after a minute it has transformed some 300 pts or so… (code is based on the center line of the cylinder (its bottom ( = cp) and highest point (hp) ) :

            ' Make Planes
            Dim cp_up As New Point3d(cp)
            cp_up.Z += 10
            Dim z_vect As Vector3d = cp_up - cp
            Dim tilt_vect As Vector3d = hp - cp
            ' Planes + xform
            Dim z_plane As New Plane(cp, z_vect)
            Dim tilt_plane As New Plane(cp, tilt_vect)
            Dim xform As Transform = Transform.PlaneToPlane(tilt_plane, z_plane)
            ' Transform
            cp.Transform(xform)
            hp.Transform(xform)
            For Each pt As Point3d In pts_tmp
                pt.Transform(xform)
            Next

BTW, I tried the rhdoc.UndoRecordingEnabled = False thing, but it didn’t do any difference (it’s probably not involved when scripting?).

// Rolf

This is my test script for Point3d’s,
as you can see, you have to select the point objects in Rhino
The scripts also draws the transformed points then.

import Rhino
import scriptcontext
import System

def main():
  gob = Rhino.Input.Custom.GetObject()
  gob.AcceptNothing( True )
  gob.SetCommandPrompt( 'Points (by Point3d) ?' )
  gob.GetMultiple( 0, 0 )
  res = gob.Result()
  if res == Rhino.Input.GetResult.Object:
    obrefs = gob.Objects()
    pts = [ obref.Point().Location for obref in obrefs ]
    pl0 = Rhino.Geometry.Plane(
        Rhino.Geometry.Point3d( 23, 56, 77 ),
        Rhino.Geometry.Point3d( 102, 44,  21 ),
        Rhino.Geometry.Point3d( 7, 221, 66 ) )
    pl1 = Rhino.Geometry.Plane(
        Rhino.Geometry.Point3d( 10, 99, 22 ),
        Rhino.Geometry.Point3d( 2, 101,33 ),
        Rhino.Geometry.Point3d( 220, 12, 88 ) )
    xf = Rhino.Geometry.Transform.PlaneToPlane( pl0, pl1 )
    sw = System.Diagnostics.Stopwatch.StartNew()
    for pt in pts:
      pt.Transform( xf )
    msec = sw.ElapsedMilliseconds
    print( '%.0f ms' % msec )
    for pt in pts:
      scriptcontext.doc.Objects.AddPoint( pt )
    scriptcontext.doc.Views.Redraw()
 
main()

EDIT: Ah, as you may guess, the transform planes are purely … random :wink:

About the undo buffer, AFAIK, it only stores regular Rhino objects ( in Rhino’s ObjectTable )
It should not have anything to do with plain RhinoCommon Geometry objects.
… I also think that GH is independent from the undo buffer, until you bake something…
But this is just my guess, …I know little about GH … :worried:

Yes, but you do the same thing as me, so your example applies.

But I now debugged in realtime and found that it is not that transform that takes time, it is either one of two problems that occor depening on how I treat the point list :

A. If I transform the same list (InPts) as I used when reading the in-port ( DA.GetDataList(IN_Points, InPts) ) then when I try to access or manipulate the transformed points an exception is thrown (the ā€œdata has changedā€, well, they should have been transformed, yes…).

B. If I copy the point list before transform (into a pts_tmp-list, as in my code example above), then after the transform any manipulation if the points, like in a for-each loop for example, takes ā€œforeverā€ (at least 10 minutes), which should take say 50 ms in total, at most.


Edit: OK, in §A referenced a property. Accessing the variable directly (m_Pts) fixes the problem, the point list does no longer complains about members (points) being changed.

But, the points doesn’t seem to be transformed at all! As shown in the picture, only the Cylinder is actually transformed, then in my code I filter points inside the Cylinder BBox, (the green points) and at last, I transform
an ā€œxform_inverseā€ to revert the Cylinder (and the points) to initial orientation, but, the points doesn’t seem to be translated at all

Fig.1. The green points where ā€œcapturedā€ inside the Cylinder BoundingBox while the Cylinder was transformed to planar orientation). If also the points had been transformed, the green points would have the same ā€œinclinationā€ as the Cylinder in the picture.

// Rolf

10k points, 68 milliseconds to test for cylinder inclusion.

cylindertest.gh (14.9 KB)

Very good!

Running this on my test mesh it filters in less than 300 ms. That’s good for 46.000+ points.

Fig.1. Added mesh.Deconstruct and Rotation to the Cylinder.

Fig.1. Filtered result in green :

Many huge thanks for this! I just learned a lot.

// Rolf

I didn’t see Mesh.IsPointInside mentioned here so I did a little test with a tilted cylinder brep, extracted render mesh, and points extracted from a point grid 50x50x50:

Mesh.IsPointInside is faster here by quite a bit, but maybe not fast/accurate enough for your needs, but it is built-in.

I used python for the test though, not VB:

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino as R

import datetime


def mesh_inclusion():
    mesh_guid = rs.GetObject('select mesh')
    point_guids = rs.GetObjects('select points')
    
    mesh_geo = rs.coercegeometry(mesh_guid)
    
    included_count = 0
    excluded_count = 0
    start_time = datetime.datetime.now()
    
    for point_guid in point_guids:
        point_geo = rs.coercegeometry(point_guid).Location
        if mesh_geo.IsPointInside(point_geo, .01, True):
            included_count += 1
        else:
            excluded_count += 1
    
    end_time = datetime.datetime.now()
    total_time = (end_time - start_time).total_seconds()
    print 'Mesh Inclusion time: {}s  |  included: {}, excluded: {}'.format(total_time, included_count, excluded_count)
    
def brep_inclusion():
    brep_guid = rs.GetObject('select brep')
    point_guids = rs.GetObjects('select points')
    
    brep_geo = rs.coercegeometry(brep_guid)
    
    included_count = 0
    excluded_count = 0
    start_time = datetime.datetime.now()
    
    for point_guid in point_guids:
        point_geo = rs.coercegeometry(point_guid).Location
        if brep_geo.IsPointInside(point_geo, .01, True):
            included_count += 1
        else:
            excluded_count += 1
    
    end_time = datetime.datetime.now()
    total_time = (end_time - start_time).total_seconds()
    print 'Brep Inclusion time: {}s  |  included: {}, excluded: {}'.format(total_time, included_count, excluded_count)
    
    
mesh_inclusion()
brep_inclusion()

Also I used a very old version of 6.0 WIP.

Hi Nathan,

Yes, the built-in components would have been the first choice, but way too slow. My meshes are not solid (very, very bad quality at ttimes, full of holes) so Mesh.IsPointInside is not an option even if it had been faster than the handcrafted one by @Sweris_Waddanders, which shows awesome performance, and it will probably be faster yet when implemented in a compiled GH component, since the ports are not cached in the same way as in the script components.

But all suggestions are welcome. I learn a lot of Rhino & GrassHopper programming from all these examples, so don’t hold back! :slight_smile:

// Rolf

Oh ok…I thought you were using a cylinder brep for the inclusion test. If so you could always get a closed mesh from the brep.

I generate the cylinder from the two tilted (input) end points C, H defining the cylinder axis + (input) radius R.

Now I have implemented the ClosestParameter concept in my components, and the result is just awesome.

  1. Mesh deconstruct 15 ms (46.000+ vertices)
  2. Filtered for (tilted) Cylinder Inclusion in 20 ms (and less)

Fig.2 … and the filtered result :

Absolutely perfect! Now we are talking business. Wow! :sunglasses:

// Rolf

1 Like

Hi

If your brep is Cylinder then you can use pure geometry to detect if point is inside Cylinder.
All you have to know is how to use Vector3d dot product {in C# you can use operator *, write it as V1 * V2, otherwise you can use Vector3d.Multiply(Vector3d, Vector3d) method} and Pythagoras’s theorem.
Here is code/method in C# which determines which point is inside cylinder and which is not.
On very avarage Win7-32 PC to test array of 5 milions (5.000.000) of points took less than 1 second.

    //
    public static bool[] IsPointInsideCylinder(Cylinder cylinder, Point3d[] points, double tolerance)
    {
        bool[] pointStatus = new bool[points.Length];
        //pointStatus[i] = true => points[i] is inside of cylinder

        Vector3d N = cylinder.Axis; //unit size vector
        Point3d C1 = cylinder.Center;
        Circle baseCircle = cylinder.CircleAt(cylinder.Height1);
        double r = baseCircle.Radius;
        Point3d C2 = C1 + N * cylinder.TotalHeight;

        for (int i = 0; i < points.Length; i++)
        {
            Point3d P = points[i];
            Vector3d V1 = P - C1;
            //h1 = length of projection of vector V1 to cylinder.Axis
            double h1 = V1 * N;
            //d = distance between point and its projection to cylinder.Axis                           
            double d = Math.Sqrt(V1.Length * V1.Length - h1 * h1);
            if (d >= r + tolerance)
            {
                pointStatus[i] = false;
                continue;
            }
            Vector3d V2 = P - C2;
            //h2 = length of projection of vector V2 to cylinder.Axis
            double h2 = V2 * N;
            if (h2 + h1 >= cylinder.TotalHeight + tolerance)
            {
                pointStatus[i] = false;
            }
            else
            {
                pointStatus[i] = true;
            }
        }
        //
        return pointStatus;
    }

In the code you can skip some variable definition but I use them to match attached image where you can see which variable corespods to which property of Cylinder.

For all other primitive solids (sphere, cone,…) there is similar way to solve Is_point_in_solid problem.
And it will be ULTRA fast algorithm if coded properly.

P.S. I would suggests you to give a try to C# with RhinoCommon - if you want to get maxiumum from Rhino in most easy way (=the least expensive way}…

Radovan

1 Like

Wow, this looks interesting, thank you for this hint!

Too late to study this solution tonight though, but since my last post I’m down at 8 ms and less, with the current ClosestParameter solution. It feels like I can rest until tomorrow before looking into your solution… :thinking:

OK, ok , ok I get it. :slight_smile:

But what I do not understand is what it is that gets better if the same compiler makes the same byte code from VB.NET?

I also know of at least one benefit from using VB.NET - and that is that VB feels familiar ā€œas isā€. I’ve done quite some VBA with Excel and ā€œmy toolā€ Delphi is also quite close to VB, so until I get some more concrete reasons for abandoning VB I may hesitate… At least I won’t rush. :slight_smile:

I really do apprechiate all advice and I will for sure take a closer look at this solution. I’m learning fast right now thanks to all of you guys. Wow.

// Rolf

But what I do not understand is what it is that gets better if the same compiler makes the same byte code from VB.NET?

You’re absolutely right, there is no difference after compilation.

ā€˜ā€™'Side-Topic-Mode-On

I feel in the same way as I started with Rhinoscript (VBScript) long time ago. It seams that Microsoft is pushing to give more and more protagonism to C#; for instance, you cannot get certain .NET certificates from them using VB.NET any more. Besides that and that there are more resources in the network using C#, I don’t see any good reason yet to jump. I have some experience with JAVA/P5 so I imagine that the transition will not be very hard.

ā€˜ā€™'Side-Topic-Mode-Off

1 Like

I used to program in asembler, VB, TurboPascal, Deplphi, C/C++, even learn is school Fortran and Cobol (it was long time ago). For me C# just seems more elegant and disciplined while VB.Net seems kind of sloppy (becasue VB.net uses BASIC-style syntax and C# uses C-style syntax).
Also C# has some syntactic sugar for some of the more core classes in the .NET framework, while VB.net does not have as much.
Once you have your code written in C#, migrating it to lets say Java is easy so if somenbody is going to learn new language I would recommend C#. VB may look simpler at first, straight forward function names, statements implicitly end with line breaks or end statements, but for me this is in fact making it more taxing to read than other languages.
I made swith to C# and I would not go back to VB now.
But some people preffer coffee with sugar, some without. So if VB.Net is more acceptable for somebody because of its syntax it is nothing wrong becausse at the end after ā€˜compiling’ it to CIL you will most probably get the same code.

2 Likes

That was two concrete arguments. And not so off-topic since problems solving includes getting to solutions as straight-forward as possible. Which includes languages that you kan express your solutions in while at sleep. :dizzy_face:[quote=ā€œRadovanG, post:40, topic:45714ā€]
I made swith to C# and I would not go back…
[/quote]
You mean that C# is a bit like Hotel California? Why does these words start ringing in my ears… :

"Last thing I remember, I was
Running for the door
I had to find the passage back
To the place I was before
"Relax, " said the night man,
"We are programmed to receive.
You can check-out any time you like,
But you can never leave! "

:slight_smile:

// Rolf

2 Likes