0

I have a question about the math involved to copy a path. Let's say I have this path:

https://i.stack.imgur.com/4aYgX.jpg

I want an exact copy of this path besides the black one. I wrote a small C# program that calculates the angle between two points. Depending on the angle, an offset to the X or Y value is added. It kind of works, this is the result:

https://i.stack.imgur.com/6uaq1.jpg

As you can see, it's not that pretty. Now, my real question is: What is the proper math to use for this?

Hopefully someone knwos an answer, because I'm kinda stuck on this one. Regards, Sascha

Code:

void Plot(List<Point> points)
    {
        Graphics g = pictureBox.CreateGraphics();
        g.Clear(Color.White);

        for (int i = 0; i < points.Count - 1; i++)
        {
            g.DrawLine(Pens.Black, points[i], points[i + 1]);
        }

        List<Point> points2 = new List<Point>();
        for (int i = 0; i < points.Count - 1; i++)
        {
            var angle = getAngleFromPoint(points[i], points[i + 1]);
            Debug.WriteLine(angle);

            if (angle < 180 && angle >= 135)
            {
                points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
            }
            if (angle < 135 && angle >= 90)
            {
                if (points[i].Y < points[i + 1].Y)
                {
                    points2.Add(new Point(points[i].X - OFFSET / 2, points[i].Y + OFFSET));
                }
                else
                {
                }                   
            }
            if (angle < 90 && angle >= 45)
            {
                if (points[i].Y < points[i + 1].Y)
                {
                    points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
                }
                else
                {
                    points2.Add(new Point(points[i].X + OFFSET, points[i].Y));
                }
            }
            if (angle < 45 && angle >= 0)
            {
                if (points[i].Y < points[i + 1].Y)
                {
                    points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
                }
                else
                {
                    points2.Add(new Point(points[i].X + OFFSET, points[i].Y));
                }
            }
            if (angle < 360 && angle >= 315)
            {
                if (points[i].Y < points[i + 1].Y)
                {
                    points2.Add(new Point(points[i].X + OFFSET, points[i].Y));
                }
                else
                {
                    points2.Add(new Point(points[i].X + 10, points[i].Y - OFFSET));
                }
            }
            if (angle < 315 && angle >= 270)
            {
                points2.Add(new Point(points[i].X, points[i].Y - OFFSET));
            }
            if (angle < 270 && angle >= 225)
            {                    
                if (points[i].Y < points[i + 1].Y)
                {
                    points2.Add(new Point(points[i].X - OFFSET / 2, points[i].Y - OFFSET));
                }
                else
                {

                }
            }
            if (angle < 225 && angle >= 180)
            {
                if (points[i].X < points[i + 1].X)
                {
                    points2.Add(new Point(points[i].X, points[i].Y - OFFSET));
                }
                else
                {
                    if (points[i].Y < points[i + 1].Y) //      \
                    {
                        points2.Add(new Point(points[i].X - OFFSET, points[i].Y));
                    }
                    else
                    {

                    }
                }
            }
        }

        for (int i = 0; i < points2.Count - 1; i++)
        {
            g.DrawLine(Pens.Red, points2[i], points2[i + 1]);
        }
    }

I think if i decrease the angles (from 45 degree steps to maybe 30 degrees) I could imnprove the result, but there must be a better solution.

inexcitus
  • 2,471
  • 2
  • 26
  • 41

2 Answers2

2

I suppose one way to tackle this is to split it into line-pairs (ie: three points)

Find the parallel line (at distance d) for each line in the pair. Then find where these parallel lines intersect to give you the location of a point on the new line.

In very rough psuedo-code:

points a, b, c
distance d

lineab = findLineParallelTo(line(a,b), d)
linebc = findLineParallelTo(line(b,c), d)

return intersect(lineab, linebc)
Jack
  • 2,625
  • 5
  • 33
  • 56
0

I implemented the solution from @Jack and it works great:

public class Line
{
    public PointF P { get; private set; }
    public PointF Q { get; private set; }

    public float Pitch
    {
        get; private set;
    }

    public Line()
    {

    }

    public Line(float px, float py, float qx, float qy) : this(new PointF(px, py), new PointF(qx, qy))
    {

    }

    public Line(PointF p, PointF q)
    {
        P = p;
        Q = q;
    }

    #region Methods

    /// <summary>
    /// http://stackoverflow.com/questions/2825412/draw-a-parallel-line
    /// </summary>
    public Line FindParallelLine(float distance)
    {
        float length = (float)Math.Sqrt((P.X - Q.X) * (P.X - Q.X) + (P.Y - Q.Y) * (P.Y - Q.Y));

        // This is the second line
        float px = P.X + distance * (Q.Y - P.Y) / length;
        float qx = Q.X + distance * (Q.Y - P.Y) / length;
        float py = P.Y + distance * (P.X - Q.X) / length;
        float qy = Q.Y + distance * (P.X - Q.X) / length;

        return new Line(px, py, qx, qy);
    }        

    public override string ToString()
    {
        return string.Format("P({0}|{1}), Q({2}|{3}) - Pitch: {4}", P.X, P.Y, Q.X, Q.Y, Pitch);
    }

    #endregion
}

private PointF FindIntersection(Line a, Line b)
    {
        PointF A = a.P;
        PointF B = a.Q;
        PointF C = b.P;
        PointF D = b.Q;

        float dy1 = B.Y - A.Y;
        float dx1 = B.X - A.X;
        float dy2 = D.Y - C.Y;
        float dx2 = D.X - C.X;            

        // Check whether the two line parallel.
        if (dy1 * dx2 == dy2 * dx1)
        {
            return PointF.Empty;
        }
        else
        {
            float x = ((C.Y - A.Y) * dx1 * dx2 + dy1 * dx2 * A.X - dy2 * dx1 * C.X) / (dy1 * dx2 - dy2 * dx1);
            float y = A.Y + (dy1 / dx1) * (x - A.X);
            return new PointF(x, y);
        }
    }

    private PointF FindIntersection(PointF a, PointF b, PointF c, float distance)
    {
        Line line1 = new Line(a, b);
        Line line2 = new Line(b, c);

        Line parallel = line1.FindParallelLine(distance);
        Line parallel2 = line2.FindParallelLine(distance);

        return FindIntersection(parallel, parallel2);
    }

    private List<PointF> FindIntersections(PointF[] points, float distance)
    {
        List<PointF> intersections = new List<PointF>();

        for (int i = 0; i < points.Length - 2; i++)
        {
            PointF intersection = FindIntersection(points[i], points[i + 1], points[i + 2], distance);
            if (!intersection.IsEmpty && !double.IsNaN(intersection.X) && !double.IsNaN(intersection.Y))
            {
                intersections.Add(intersection);
            }                
        }

        return intersections;
    }

    private PointF GetFirstPoint(PointF[] points, float distance)
    {
        Line parallel = new Line(points[0], points[1]).FindParallelLine(distance);
        return parallel.P;
    }

    private PointF GetLastPoint(PointF[] points, float distance)
    {
        Line parallel = new Line(points[points.Length - 2], points[points.Length - 1]).FindParallelLine(distance);
        return parallel.Q;
    }

Example call:

OFFSET = float.Parse(textBox1.Text);
        List<PointF> points = new List<PointF>();
        points.Add(new PointF(200, 180));
        points.Add(new PointF(160, 160));
        points.Add(new PointF(100, 160));
        points.Add(new PointF(60, 140));
        points.Add(new PointF(40, 100));
        points.Add(new PointF(80, 60));
        points.Add(new PointF(140, 100));
        points.Add(new PointF(180, 140));
        points.Add(new PointF(220, 80));

        List<PointF> intersections = FindIntersections(points.ToArray(), OFFSET);
        intersections.Insert(0, GetFirstPoint(points.ToArray(), OFFSET));
        intersections.Add(GetLastPoint(points.ToArray(), OFFSET));

        Graphics g = pictureBox.CreateGraphics();
        g.Clear(Color.White);

        g.DrawLines(Pens.Black, points.ToArray());
        // Connect the intersection points.
        g.DrawLines(Pens.Red, intersections.ToArray());

Example image:

http://imgur.com/onUstGT

Thanks again @Jack !

inexcitus
  • 2,471
  • 2
  • 26
  • 41