Curve.Contains Method too slow

,

Bit late to the party here, but here’s a good speed-up if you use the same curves(s) over and over again.

  • Measure the boundingbox of all the curves you’re testing.
  • Set up a transformation T_{map} from World XYZ onto a 2D rectangular domain of custom width w and height h, say w=1000 and h=1000.
  • Create a bitmap of w \times h and clear it so it’s completely black.
  • Transform all your curves from world space into bitmap space using T_{map} and fill them using white. You will not be wanting any anti-aliasing.
  • Then draw the edges of all the curves (again without anti-aliasing) in some third colour, for example red. Either use a single pixel thick pen, or maybe 2 pixels thick, you may have to experiment to find the thickness that never yield false negatives or positives.

At this point you have an image consisting only of black, white and red pixels with a distorted visual representation of your curves.

Now you can quickly test individual points:

  • Transform each point from World space into bitmap space using T_{map}.
  • If the mapped coordinates fall outside the bitmap dimensions (x < 0, x \geq w, y<0, y \geq h) then the point is not inside any curve.
  • If the pixel colour under the mapped coordinates is black, the point is not inside any curve.
  • If the pixel colour under the mapped coordinates is white, the point is definitely inside a curve.
  • If the pixel colour under the mapped coordinate is red, the precision of your lookup table doesn’t allow for an exact answer and you’ll have to fall back on expensive geometric testing.

Possible improvements to this scheme include the use of more colours. You could for example simply use the (A)RGB equivalents of the curve index to associate specific regions with specific input curves.

You will also want to lock the bitmap in memory if you’re using C# or VB, because GetPixel() on a memory bitmap is way faster than GetPixel() on a free bitmap.

Hi
There is also issue with complex curve and Mesh.CreateFromPlanarBoundary() method - see photo.

Sample curves are internailsed:
ComplexCurves_Mesh_Issue.gh (10.3 KB)

@RadovanG it was also discussed earlier only additional step here is check if curve is self intersecting if is cut it in pieces join and mesh sepatate pieces then join mesh into one and cast rays to joined mesh one thing i didnt checked here is what if meshray would hit mesh backface cause indeed if user will pick tricky shape mesher could produce fliped mesh and next steps to find out where normals are pointing for sure can take down performance. I’ll give a shot with @DavidRutten concept also i think it really have potencial cause all points are solved at once and theres always result without going back and checking same points again.

If you work with mesh I would suggest to try with polyline as well:

  • covert curve to polylne-like structure
  • use algorithm to check number of intersections between ray defined by point and lets say y+ vector to detect if point is inside polyline.
    I will try something and post it soon…

I write it as plugin

1 Like


1 Like

@RadovanG that means one thing that theres no point to believe gh profiler in terms of watching time for c# code indeed i didnt pushed any of solutions to VS. Did you tried other solutions as well? I just ask if not ill test them as plugs cause differences are really huge.

Gh profiler is OK, but gh is slower because of data manipulation.
I will clean up the code an post it shortly.
Code is based on curve conversion to polyline and applying algorithm like ray-shoting.
R

1 Like

@RadovanG i also wanted to ask is there any particular reason for checking points in circles? Aren’t bboxes better in this case? They should contain smaller amount of points than circles.

Because constructing such a circle is fast, while constructing boundingbox for curve is slower.
But new solution I have is not using another approach - code will be posted in few minutes.

Here is final code.
You call method pointsInsideCurvesE(points, curves) and it returns list of indices of points that are inside ate least one curve.

    public struct MyPairCoord_Id
    {
        public int Id { get; set; }
        public double Coord { get; set; }
        public MyPairCoord_Id(int _id, double _coord)
        {
            Id = _id;
            Coord = _coord;
        }
    }

    public struct MyCurveSegments
    {
        public int curveId { get; set; }
        public double[] X1 { get; set; } //start of curve segmnet x-coords
        public double[] Y1 { get; set; } //start of curve segmnet y-coords
        public double[] X2 { get; set; } //start of curve segmnet x-coords
        public double[] Y2 { get; set; } //start of curve segmnet y-coords

        //boundingBox-like coords 
        public double MinX { get; set; }            
        public double MinY { get; set; }
        public double MaxX { get; set; }
        public double MaxY { get; set; }
        public MyPairCoord_Id[] X1_Id { get; set; } //sorted start_of_curve_segmnet x-coords

        public MyCurveSegments(int crvId, PolylineCurve polyLineCrv)
        {
            curveId = crvId;
            //nymber of curve segments is plCrv.PointCount-1
            X1 = new double[polyLineCrv.PointCount-1];
            Y1 = new double[polyLineCrv.PointCount - 1];
            X2 = new double[polyLineCrv.PointCount-1];
            Y2 = new double[polyLineCrv.PointCount - 1];                
            MyPairCoord_Id[] tX1 = new MyPairCoord_Id[polyLineCrv.PointCount - 1];
            MinX = Double.MaxValue;
            MinY = Double.MaxValue;
            MaxX = Double.MinValue;
            MaxY = Double.MinValue;

            for (int i = 0; i < polyLineCrv.PointCount-1; i++)
            {
                var ptAt_i = polyLineCrv.Point(i);
                var ptAt_i_next = polyLineCrv.Point(i+1);
                //start point has to be smaller then end point of segment (for purpose of our algorithm)
                if (ptAt_i> ptAt_i_next)
                {
                    X1[i] = ptAt_i_next.X;
                    Y1[i] = ptAt_i_next.Y;
                    X2[i] = ptAt_i.X;
                    Y2[i] = ptAt_i.Y;
                }
                else
                {
                    X1[i] = ptAt_i.X;
                    Y1[i] = ptAt_i.Y;
                    X2[i] = ptAt_i_next.X;
                    Y2[i] = ptAt_i_next.Y;
                }
                MinX = Math.Min(MinX, X1[i]);
                MaxX = Math.Max(MaxX, X2[i]);
                MinY = Math.Min(MinY, Math.Min(Y2[i], Y1[i]) );
                MaxY = Math.Max(MaxY, Math.Max(Y2[i], Y1[i]));
                tX1[i] = new MyPairCoord_Id(i, X1[i]);
            }
            X1_Id = tX1.OrderBy(a => a.Coord).ToArray();
        }
    }

    private static List<int> pointsInsideCurvesE(List<Point3d> points, List<Curve> curves)
    {
        List<int> pointsIds = new List<int>();
        int mainSegmentCount = -1;
        int subSegmentCount = -1;            
        double maxAngleRadians = RhinoDoc.ActiveDoc.ModelAngleToleranceRadians;
        double maxChordLengthRatio = 1;
        double maxAspectRatio = 0;
        double tolerance = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance*10;
        double minEdgeLength = tolerance * 10;
        double maxEdgeLength = System.Double.MaxValue;
        bool keepStartPoint = true;
        //We convert curves to polyLines and populate our MyCurveSegments values
        List<MyCurveSegments> curvesSegments = new List<MyCurveSegments>();
        for (int i = 0; i < curves.Count; i++)
        {
            if (curves[i] == null  ||  !curves[i].IsClosed)
            {
                continue;
            }
            var plCrv = curves[i].ToPolyline(mainSegmentCount, subSegmentCount, maxAngleRadians, maxChordLengthRatio,
                             maxAspectRatio, tolerance, minEdgeLength, maxEdgeLength, keepStartPoint);
            if (plCrv!=null)
            {
                MyCurveSegments segments = new MyCurveSegments(i, plCrv);
                curvesSegments.Add(segments);               
            }
        } 

        for (int i = 0; i < points.Count; i++)
        {
            var pt = points[i];
            foreach (var cSeg in curvesSegments)
            {                    
                int topIntersections = 0;
                //we will check if point is inside curve only if point is inside our BoundingBox
                if (pt.X>= cSeg.MinX && pt.X<=cSeg.MaxX && pt.Y>=cSeg.MinY && pt.Y<= cSeg.MaxY)
                {
                    List<int> intersectingSegmentsId = new List<int>();
                    for (int k = 0; k < cSeg.X1_Id.Length; k++)
                    {
                        if (cSeg.X1_Id[k].Coord < pt.X && cSeg.X2[cSeg.X1_Id[k].Id]>= pt.X)
                        {
                            intersectingSegmentsId.Add(cSeg.X1_Id[k].Id );
                        }
                    }

                    if (intersectingSegmentsId.Count() < 1)
                        continue;
                    foreach (var id in intersectingSegmentsId)
                    {
                        if (pt.Y <= cSeg.Y1[id] && pt.Y <= cSeg.Y2[id])
                        {
                            //point is above intesecting segment/line
                            topIntersections++;
                        }
                        else
                        {
                            if ( !(pt.Y > cSeg.Y1[id] && pt.Y > cSeg.Y2[id]) )
                            {
                                //find out if point is wbelow or above intersecting point
                                var k = (cSeg.Y2[id] - cSeg.Y1[id]) / (cSeg.X2[id] - cSeg.X1[id]);
                                var b = cSeg.Y2[id] - k * cSeg.X2[id];
                                var y = k * pt.X + b;
                                if (y > pt.Y)
                                {
                                    //point is above intesecting segment/line
                                    topIntersections++;
                                }
                            }
                        }
                    }
                }
                int remainder;
                Math.DivRem(topIntersections, 2, out remainder);
                // point is inside curve if number of intersections between ray (defined with point and y+ vector direction) is odd
                if (remainder==1)
                {
                    pointsIds.Add(i);
                    break;
                }
            }
        }
        return pointsIds;
    }
2 Likes

GH file with the same code (slight modification because of way how gh treats fields) will in a minute.

gh is slower but arround 100-120 ms

gh-file
PointInInsideCurve03.gh (239.1 KB)

Here is modified version with Parallel.For loop instead of serial. Speed of execution is improved almost double.
10 000 points / 200 crvs

300 000 points / 200 crvs

PointInInsideCurve04.gh (240.8 KB)

10 Likes

And the winner is … @RadovanG ! Outstanding performance man ! I really appreciate your help guys! I received more than i would expect :slight_smile: Thank you @RadovanG @qythium @RIL @DavidRutten for your input, help and involvement in this topic!

1 Like

In these “performance competitions” we are all winners.

This is part of why this forum is awesome.

Special congrats to @RadovanG! :1st_place_medal:

// Rolf

3 Likes

Thanks for this! It works very well.

By any chance, do you know if it’s possible for this code to output seperately the indeces of the points that are NOT contained in the curves as well?

All in all, there will be two outputs for the C# component. One is the indeces of the points that ARE contained and the other output are the indeces of the points that are NOT contained.

Thanks again for that component and cheers!

Just use Cull Index component on initial points and you’ll get the difference:

Thanks for the suggestion! But I don’t really need the points outside of the curve boundaries. What I actually need to get from this is really the indeces of those non-contained points themselves.

The reason for this is because I’m planning to re-create the data structure that I have that was lost when ran through this C# component. Even though it does run very fast, it outputs a flattened list of contained points which is not very useful for me since, in my case, the data structure is needed to be kept.

What I’m planning to do is re-create my data structure by matching a list of booleans (True for the indeces of the contained points and False for the indeces of the uncontained points) with that of the original dataset’s. After that, I would dispatch my original dataset and retain my original data structure.

Thanks for the idea though! I think we can do something similar by creating a series of numbers that matches thr length of my original dataset and use the indeces we got from the C# component and cull them out. I’ll try this out first thing tomorrow and update this thread if it’s fast enough. The calculation speed is important for me here so that’s why I preferred the C# component to do the job if possible.

Thanks again! I’ll write again tomorrow. Cheers!