复制距离为 d 的路径的数学运算

Math to copy a path with a distance d

我对复制路径所涉及的数学有疑问。 假设我有这条路:

http://imgur.com/a/42l0t

除黑色路径外,我还想要此路径的精确副本。我写了一个小的 C# 程序来计算两点之间的角度。根据角度,添加 X 或 Y 值的偏移量。 有点效果,这是结果:

http://imgur.com/bJQDCgq

如您所见,它并不那么漂亮。 现在,我真正的问题是:用于此的正确数学是什么?

希望有人知道答案,因为我有点卡在这个问题上了。 问候, 萨沙

代码:

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]);
        }
    }

我认为如果我减小角度(从 45 度步长到 30 度左右)我可以改进结果,但必须有更好的解决方案。

我想解决这个问题的一种方法是将它分成线对(即:三个点)

为这对直线中的每条直线找出平行线(距离为 d)。然后找到这些平行线相交的位置,为您提供新线上的点位置。

非常粗糙的伪代码:

points a, b, c
distance d

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

return intersect(lineab, linebc)

我实施了@Jack 的解决方案,效果很好:

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>
    /// 
    /// </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;
    }

调用示例:

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());

示例图片:

http://imgur.com/onUstGT

再次感谢@Jack!