4

I am attempting to create a shape like the following:

Spiral

For the curves of the spiral, I am utilizing Quadratic Bezier Segments.

    PathGeometry pg1 = new PathGeometry();
    PathFigure pf1 = new PathFigure()
    {
        StartPoint = new Point(Convert.ToDouble(middle) + 500, Convert.ToDouble(middle) + 500)
    };
    PathSegmentCollection psc1= new PathSegmentCollection();
    QuadraticBezierSegment arcs1 = new QuadraticBezierSegment()
    {
        Point1 = new Point(100, 560),
        Point2 = new Point(pf.StartPoint.X - 300, pf.StartPoint.Y + 200)
    };
    psc1.Add(arcs1);
    pf1.Segments = psc1;
    pg1.Figures.Add(pf1);
    Path spiral1 = new Path()
    {
        Data = pg1,
        Stroke = Brushes.White,
        StrokeThickness = 1.5
    };
    MainScrn.Children.Add(spiral1);

Which outputs an appropriate curve for one of the Paths: animation path displayed

I'm sure I marked this up wrong, but here is where and how the variables above are associated with the Bezier Curve.

Quadratic Bezier Curves Points

Now what I want is the points along the curve.

Path with points on segment

And I can't receive that from the object. I'm trying to collect these points so that I can animate movement of objects along the path of the Bezier Curve and have them stop at different points of the curve. How can I achieve this?

Rich
  • 4,134
  • 3
  • 26
  • 45
  • Is computing them manually out of the question?\ – ChiefTwoPencils May 20 '15 at 02:30
  • Yes, because I need to be able to add and subtract objects along the path. e.g. Path 1 has 7 objects, Path 8 has 13 objects, etc.. – Rich May 20 '15 at 02:33
  • Yeah, but as the new answer I think is getting at is that you can control how many points make up the curve. So you'd subdivide the lines according to how many objects needed. – ChiefTwoPencils May 20 '15 at 02:38

2 Answers2

3

Suppose you want to compute a point one fifth (or any amount) of the way along the cubic Bezier curve from pf1.StartPoint (P1) to arcs1.Point2 (P3) with the control point arcs1.Point1 (P2).

You do it like this:

  • Compute a point one fifth of the way along the straight line from P1 to P2, and call that A
  • Compute a point one fifth of the way along the straight line from P2 to P3, and call that B
  • Compute a point one fifth of the way along the straight line from A to B, and that's your answer.

You can reduce this to a polynomial formula that you can just plug values into and get your answer out of, but this is perhaps more geometrically intuitive and you can probably implement it easily with built in functions in the Point class.

Community
  • 1
  • 1
samgak
  • 23,944
  • 4
  • 60
  • 82
  • Ok. Thank you for this, this helps me find a point on the path, but how do I make sure each point is evenly spaced from the path? – Rich May 20 '15 at 02:46
  • 1
    Suppose you want 10 points on the curve (including the start and end points). Start at 0 and increment your parameter by 1/9 each time, up to 1. Then use that parameter in the above formula. So first point is 1/9ths of the way, second point is 2/9ths of the way etc. – samgak May 20 '15 at 02:48
  • @Rich, my apologies, that method won't give exactly equal spacing on some curves due to the non-linear behaviour of the function, although it will give an approximation. Getting precisely equal spacing is more complicated, an algorithm for it is described here: http://pomax.github.io/bezierinfo/#tracing – samgak May 20 '15 at 03:00
  • Well, you got me to the equations for it, so i'm good enough with that. Thanks again. – Rich May 20 '15 at 03:05
  • FYI. Here's the output by incrementing the parameter (it's not that bad). http://imgur.com/wLUnrSZ – Rich May 20 '15 at 03:40
3

Here's the complete answer. I calculated the total length of the path with 30 points spread across the path itself (thanks for @samgak for that). I found the average spaced pixel length by dividing the length by the amount of segments, then comparing it to an array of incremented calculations(avg vs next point) between each point.

Here's the output.

proper curve

Here's the code.

            PathGeometry pg1 = new PathGeometry();
            PathFigure pf1 = new PathFigure()
            {
                StartPoint = new Point(Convert.ToDouble(middle) + 500, Convert.ToDouble(middle) + 500)
            };
            PathSegmentCollection psc1= new PathSegmentCollection();
            QuadraticBezierSegment arcs1 = new QuadraticBezierSegment()
            {
                //Point1 = new Point(100, 560),
                Point1 = new Point(150, 480),
                Point2 = new Point(pf.StartPoint.X - 300, pf.StartPoint.Y + 200)
            };
            psc1.Add(arcs1);
            pf1.Segments = psc1;
            pg1.Figures.Add(pf1);
            Path spiral1 = new Path()
            {
                Data = pg1,
                Stroke = Brushes.White,
                StrokeThickness = 1.5
            };
            MainScrn.Children.Add(spiral1);
            Rectangle[] pnt = new Rectangle[30];
            float growth = (float)1 / (float)30;
            float loc = 0;
            MessageBox.Show(growth.ToString());
            double lenOfpath = 0;
            Point pntA = new Point(0, 0);
            int segments = 8; segments++;
            float avgspace = 0;
            for (int length = 0; length < pnt.Count(); length++)
            {
                pnt[length] = new Rectangle();
                pnt[length].Fill = Brushes.Red;
                pnt[length].Width = 10;
                pnt[length].Height = 10;
                double t = loc;
                double left = (1 - t) * (1 - t) * pf.StartPoint.X + 2 * (1 - t) * t * arcs1.Point1.X + t * t * arcs1.Point2.X;
                double top = (1 - t) * (1 - t) * pf.StartPoint.Y + 2 * (1 - t) * t * arcs1.Point1.Y + t * t * arcs1.Point2.Y;
                MainScrn.Children.Add(pnt[length]);
                Canvas.SetLeft(pnt[length], left);
                Canvas.SetTop(pnt[length], top);
                loc = loc + growth;
                if (length > 0)
                {
                    double x10 = Canvas.GetLeft(pnt[length - 1]);
                    double x20 = Canvas.GetLeft(pnt[length]);
                    double y10 = Canvas.GetTop(pnt[length - 1]);
                    double y20 = Canvas.GetTop(pnt[length]);
                    lenOfpath = lenOfpath + Math.Sqrt(Math.Pow(x20 - x10, 2) + Math.Pow(y20 - y10, 2));
                    avgspace = ((float)lenOfpath / (float)segments);
                }
            }
            for (int length = 1; length < pnt.Count(); length++)
            {

                double total = 0;
                double[] smallestpos = new double[pnt.Count()-1];
                for (int digger = length + 1; digger < pnt.Count(); digger++)
                {
                    double x11 = Canvas.GetLeft(pnt[length]);
                    double x22 = Canvas.GetLeft(pnt[digger]);
                    double y11 = Canvas.GetTop(pnt[length]);
                    double y22 = Canvas.GetTop(pnt[digger]);
                    smallestpos[digger-1] = Math.Sqrt(Math.Pow(x22 - x11, 2) + Math.Pow(y22 - y11, 2));
                }
                int takeposition = FindClosest(avgspace, smallestpos);
                double min = smallestpos[takeposition];
                while (length < (takeposition+1))
                {
                    pnt[length].Visibility = System.Windows.Visibility.Hidden;
                    length++;
                }
            }    
        }
        public static int FindClosest(float given_number, double[] listofflts)
        {
            // Start min_delta with first element because it's safer
            int min_index = 0;
            double min_delta = listofflts[0] - given_number;
            // Take absolute value of the min_delta
            if (min_delta < 0)
            {
                min_delta = min_delta * (-1);
            }

            // Iterate through the list of integers to find the minimal delta
            // Skip first element because min_delta is set with first element's
            for (int index = 1; index < listofflts.Count(); index++)
            {
                float cur_delta = (float)listofflts[index] - (float)given_number;
                // Take absolute value of the current delta
                if (cur_delta < 0)
                {
                    cur_delta = cur_delta * (-1);
                }

                // Update the minimum delta and save the index
                if (cur_delta < min_delta)
                {
                    min_delta = cur_delta;
                    min_index = index;
                }
            }
            return min_index;
        }

With Octagons:

completed.

Once all sides are completed:

enter image description here

Rich
  • 4,134
  • 3
  • 26
  • 45
  • +1 Looks good but perhaps you should set float growth = (float)1 / (float)29; so that you calculate the point right at the end of the curve? (the last value of length in the loop will be 29). – samgak May 20 '15 at 05:38
  • You know, after I finished it and I started tweeking the parameters, changing that to (float)29 didn't help adjust it to the length. But it's ok. I got the Output I was looking for. Check out the finished path above. I've even got it animated now! :) Thanks again for everything. – Rich May 20 '15 at 14:34