Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calculate a point along the line A-B at a given distance from A

I'm going quite mad trying to calculate the point along the given line A-B, at a given distance from A, so that I can "draw" the line between two given points. It sounded simple enough at the outset, but I can't seem to get it right. Worse still, I don't understand where I've gone wrong. Geometry (and math in general) is NOT my strong suite.

I have read similar questions and there answers on SO. In fact I lifted my current implementation of CalculatePoint function directly from Mads Elvheim's answer to: Given a start and end point, and a distance, calculate a point along a line (plus a correction in a later comment - if I understand him correctly) because my indepedent attempts to solve the problem were getting me nowhere, except a first class express ticket frusterpationland.

Here's my UPDATED code (please see the EDIT notes a bottom of post):

using System;
using System.Drawing;
using System.Windows.Forms;

namespace DrawLines
{
    public class MainForm : Form
    {
        // =====================================================================
        // Here's the part I'm having trouble with. I don't really understand
        // how this is suposed to work, so I can't seem to get it right!
        // ---------------------------------------------------------------------

        // A "local indirector" - Just so I don't have go down and edit the 
        // actual call everytime this bluddy thing changes names.
        private Point CalculatePoint(Point a, Point b, int distance) {
            return CalculatePoint_ByAgentFire(a, b, distance);
        }

        #region CalculatePoint_ByAgentFire
        //AgentFire: Better approach (you can rename the struct if you need):
        struct Vector2
        {
            public readonly double X;
            public readonly double Y;
            public Vector2(double x, double y) {
                this.X = x;
                this.Y = y;
            }
            public static Vector2 operator -(Vector2 a, Vector2 b) {
                return new Vector2(b.X - a.X, b.Y - a.Y);
            }
            public static Vector2 operator *(Vector2 a, double d) {
                return new Vector2(a.X * d, a.Y * d);
            }
            public override string ToString() {
                return string.Format("[{0}, {1}]", X, Y);
            }
        }
        // For getting the midpoint you just need to do the (a - b) * d action:
        //static void Main(string[] args)
        //{
        //    Vector2 a = new Vector2(1, 1);
        //    Vector2 b = new Vector2(3, 1);
        //    float distance = 0.5f; // From 0.0 to 1.0.
        //    Vector2 c = (a - b) * distance;
        //    Console.WriteLine(c);
        //}
        private Point CalculatePoint_ByAgentFire(Point a, Point b, int distance) {
            var vA = new Vector2(a.X, a.Y);
            var vB = new Vector2(b.X, b.Y);
            double lengthOfHypotenuse = LengthOfHypotenuseAsDouble(a,b);
            double portionOfDistanceFromAtoB = distance / lengthOfHypotenuse;
            var vC = (vA - vB) * portionOfDistanceFromAtoB;
            Console.WriteLine("vC="+vC);
            return new Point((int)(vC.X+0.5), (int)(vC.Y+0.5));
        }
        // Returns the length of the hypotenuse rounded to an integer, using
        // Pythagoras' Theorem for right angle triangles: The length of the
        // hypotenuse equals the sum of the square of the other two sides.
        // Ergo: h = Sqrt(a*a + b*b)
        private double LengthOfHypotenuseAsDouble(Point a, Point b) {
            double aSq = Math.Pow(Math.Abs(a.X - b.X), 2); // horizontal length squared
            double bSq = Math.Pow(Math.Abs(b.Y - b.Y), 2); // vertical length  squared
            return Math.Sqrt(aSq + bSq); // length of the hypotenuse
        }

        #endregion

        //dbaseman: I thought something looked strange about the formula ... the question 
        //you linked was how to get the point at a distance after B, whereas you want the
        //distance after A. This should give you the right answer, the start point plus 
        //distance in the vector direction.
        //
        // Didn't work as per: http://s1264.photobucket.com/albums/jj496/corlettk/?action=view&current=DrawLinesAB-broken_zps069161e9.jpg
        //
        private Point CalculatePoint_ByDbaseman(Point a, Point b, int distance) {
            // a. calculate the vector from a to b:
            double vectorX = b.X - a.X;
            double vectorY = b.Y - a.Y;
            // b. calculate the length:
            double magnitude = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
            // c. normalize the vector to unit length:
            vectorX /= magnitude;
            vectorY /= magnitude;
            // d. calculate and Draw the new vector, which is x1y1 + vxvy * (mag + distance).
            return new Point(
                (int)((double)a.X + vectorX * distance)     // x = col
              , (int)((double)a.Y + vectorY * distance)     // y = row
            );
        }

        // MBo: Try to remove 'magnitude' term in the parentheses both for X and for Y expressions.
        //
        // Didn't work as per: http://s1264.photobucket.com/albums/jj496/corlettk/?action=view&current=DrawLinesAB-broken_zps069161e9.jpg
        //
        //private Point CalculatePoint_ByMBo(Point a, Point b, int distance) {
        //    // a. calculate the vector from a to b:
        //    double vectorX = b.X - a.X;
        //    double vectorY = b.Y - a.Y;
        //    // b. calculate the length:
        //    double magnitude = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
        //    // c. normalize the vector to unit length:
        //    vectorX /= magnitude;
        //    vectorY /= magnitude;
        //    // d. calculate and Draw the new vector, which is x1y1 + vxvy * (mag + distance).
        //    return new Point(
        //        (int)(  ((double)a.X + vectorX * distance)  +  0.5  )
        //      , (int)(  ((double)a.X + vectorX * distance)  +  0.5  )
        //    );
        //}

        // Didn't work
        //private Point CalculatePoint_ByUser1556110(Point a, Point b, int distance) {
        //    Double magnitude = Math.Sqrt(Math.Pow(b.Y - a.Y, 2) + Math.Pow(b.X - a.X, 2));
        //    return new Point(
        //        (int)(a.X + distance * (b.X - a.X) / magnitude + 0.5)
        //      , (int)(a.Y + distance * (b.Y - a.Y) / magnitude + 0.5)
        //    );
        //}

        // didn't work
        //private static Point CalculatePoint_ByCadairIdris(Point a, Point b, int distance) {
        //    // a. calculate the vector from a to b:
        //    double vectorX = b.X - a.X;
        //    double vectorY = b.Y - a.Y;
        //    // b. calculate the proportion of hypotenuse
        //    double factor = distance / Math.Sqrt(vectorX*vectorX + vectorY*vectorY);
        //    // c. factor the lengths
        //    vectorX *= factor;
        //    vectorY *= factor;
        //    // d. calculate and Draw the new vector,
        //    return new Point((int)(a.X + vectorX), (int)(a.Y + vectorY));
        //}

        // Returns a point along the line A-B at the given distance from A
        // based on Mads Elvheim's answer to:
        // https://stackoverflow.com/questions/1800138/given-a-start-and-end-point-and-a-distance-calculate-a-point-along-a-line
        private Point MyCalculatePoint(Point a, Point b, int distance) {
            // a. calculate the vector from o to g:
            double vectorX = b.X - a.X;
            double vectorY = b.Y - a.Y;
            // b. calculate the length:
            double magnitude = Math.Sqrt(vectorX * vectorX + vectorY * vectorY);
            // c. normalize the vector to unit length:
            vectorX /= magnitude;
            vectorY /= magnitude;
            // d. calculate and Draw the new vector, which is x1y1 + vxvy * (mag + distance).
            return new Point(
                (int)(((double)a.X + vectorX * (magnitude + distance)) + 0.5) // x = col
              , (int)(((double)a.Y + vectorY * (magnitude + distance)) + 0.5) // y = row
            );
        }

        // =====================================================================

        private const int CELL_SIZE = 4; // width and height of each "cell" in the bitmap.

        private readonly Bitmap _bitmap; // to draw on (displayed in picBox1).
        private readonly Graphics _graphics; // to draw with.

        // actual points on _theLineString are painted red.
        private static readonly SolidBrush _thePointBrush = new SolidBrush(Color.Red);
        // ... and are labeled in Red, Courier New, 12 point, Bold
        private static readonly SolidBrush _theLabelBrush = new SolidBrush(Color.Red);
        private static readonly Font _theLabelFont = new Font("Courier New", 12, FontStyle.Bold);

        // the interveening calculated cells on the lines between actaul points are painted Black.
        private static readonly SolidBrush _theLineBrush = new SolidBrush(Color.Black);

        // the points in my line-string.
        private static readonly Point[] _theLineString = new Point[] {
            //          x,   y
            new Point(170,  85), // A
            new Point( 85,  70), // B
            //new Point(209,  66), // C
            //new Point( 98, 120), // D
            //new Point(158,  19), // E
            //new Point(  2,  61), // F
            //new Point( 42, 177), // G
            //new Point(191, 146), // H
            //new Point( 25, 128), // I
            //new Point( 95,  24)  // J
        };

        public MainForm() {
            InitializeComponent();
            // initialise "the graphics system".
            _bitmap = new Bitmap(picBox1.Width, picBox1.Height);
            _graphics = Graphics.FromImage(_bitmap);
            picBox1.Image = _bitmap;
        }

        #region actual drawing on the Grpahics

        private void DrawCell(int x, int y, Brush brush) {
            _graphics.FillRectangle(
                brush
              , x * CELL_SIZE, y * CELL_SIZE    // x, y
              , CELL_SIZE, CELL_SIZE        // width, heigth
            );
        }

        private void DrawLabel(int x, int y, char c) {
            string s = c.ToString();
            _graphics.DrawString(
                s, _theLabelFont, _theLabelBrush
              , x * CELL_SIZE + 5   // x
              , y * CELL_SIZE - 8   // y
            );
        }

        // ... there should be no mention of _graphics or CELL_SIZE below here ...

        #endregion

        #region draw points on form load

        private void MainForm_Load(object sender, EventArgs e) {
            DrawPoints();
        }

        // draws and labels each point in _theLineString
        private void DrawPoints() {
            char c = 'A'; // label text, as a char so we can increment it for each point.
            foreach ( Point p in _theLineString ) {
                DrawCell(p.X, p.Y, _thePointBrush);
                DrawLabel(p.X, p.Y, c++);
            }
        }

        #endregion

        #region DrawLines on button click

        private void btnDrawLines_Click(object sender, EventArgs e) {
            DrawLinesBetweenPointsInTheString();
        }

        // Draws "the lines" between the points in _theLineString.
        private void DrawLinesBetweenPointsInTheString() {
            int n = _theLineString.Length - 1; // one less line-segment than points
            for ( int i = 0; i < n; ++i )
                Draw(_theLineString[i], _theLineString[i + 1]);
            picBox1.Invalidate(); // tell the graphics system that the picture box needs to be repainted.
        }

        // Draws all the cells along the line from Point "a" to Point "b".
        private void Draw(Point a, Point b) {
            int maxDistance = LengthOfHypotenuse(a, b);
            for ( int distance = 1; distance < maxDistance; ++distance ) {
                var point = CalculatePoint(a, b, distance);
                DrawCell(point.X, point.X, _theLineBrush);
            }
        }

        // Returns the length of the hypotenuse rounded to an integer, using
        // Pythagoras' Theorem for right angle triangles: The length of the
        // hypotenuse equals the sum of the square of the other two sides.
        // Ergo: h = Sqrt(a*a + b*b)
        private int LengthOfHypotenuse(Point a, Point b) {
            double aSq = Math.Pow(Math.Abs(a.X - b.X), 2); // horizontal length squared
            double bSq = Math.Pow(Math.Abs(b.Y - b.Y), 2); // vertical length  squared
            return (int)(Math.Sqrt(aSq + bSq) + 0.5); // length of the hypotenuse
        }

        #endregion

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent() {
            this.picBox1 = new System.Windows.Forms.PictureBox();
            this.btnDrawLines = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.picBox1)).BeginInit();
            this.SuspendLayout();
            // 
            // picBox1
            // 
            this.picBox1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.picBox1.Location = new System.Drawing.Point(0, 0);
            this.picBox1.Name = "picBox1";
            this.picBox1.Size = new System.Drawing.Size(1000, 719);
            this.picBox1.TabIndex = 0;
            this.picBox1.TabStop = false;
            // 
            // btnDrawLines
            // 
            this.btnDrawLines.Location = new System.Drawing.Point(23, 24);
            this.btnDrawLines.Name = "btnDrawLines";
            this.btnDrawLines.Size = new System.Drawing.Size(77, 23);
            this.btnDrawLines.TabIndex = 1;
            this.btnDrawLines.Text = "Draw Lines";
            this.btnDrawLines.UseVisualStyleBackColor = true;
            this.btnDrawLines.Click += new System.EventHandler(this.btnDrawLines_Click);
            // 
            // MainForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1000, 719);
            this.Controls.Add(this.btnDrawLines);
            this.Controls.Add(this.picBox1);
            this.Location = new System.Drawing.Point(10, 10);
            this.MinimumSize = new System.Drawing.Size(1016, 755);
            this.Name = "MainForm";
            this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
            this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
            this.Text = "Draw Lines on a Matrix.";
            this.Load += new System.EventHandler(this.MainForm_Load);
            ((System.ComponentModel.ISupportInitialize)(this.picBox1)).EndInit();
            this.ResumeLayout(false);
        }

        private System.Windows.Forms.PictureBox picBox1;
        private System.Windows.Forms.Button btnDrawLines;
        #endregion
    }

}

Sorry if it's a bit long, but this an is SSCCE exhumed from my real project, which is an implementation of the A* shortest route algorithm to run the MazeOfBolton... i.e. a maze runner.

What I actually want to do is pre-calculate a "fence" (i.e. a buffered MBR) around two given points (origin and goal) in the maze (a matrix), such that all points within the "fence" are within a given distance from "the straight line between the two points", in order to quickly eliminate the hundreds-of-thousands of possible paths which are heading away from the goal.

Note that this programming challenge closed years ago, so there's no issue with "competitive plagerism" here. No this is not homework, in fact I'm a professional programmer... I'm just WAAAAY out of my comfort zone here, even with relatively simple geometry. Sigh.

So... Please can anyone give me any pointers to help me get the CalculatePoint function to correctly: Calculate a point along the line A-B at the given distance from A?

Thanks in advance for your generosity... even in reading this far.

Cheers. Keith.


EDIT: I just updated the posted source code becuase:

(1) I just realised that it wasn't self contained. I forgot about the seperate MainForm.Designer.cs file, which I've appended to the bottom of the posted code.

(2) The latest version includes what I've tried so far, with a photobucket link to a picture of what each failure looks like... and they're all the same. Huy? WTF?

I suppose my problem may be elsewhere, like some funky windows form setting that was previously missed by everyone else because I forgot to post the designer-generated code... Except everythingelse (in my actual project) paints exactly where I expect it to, so why should a calculated point be any different. I don't know!?!?!? I'm pretty frustrated and I'm getting cranky, so I think I'll leave this for another day ;-)

Goes to show how much we routinely underestimate how much effort it'll take to make a computer do ANYthing... even just draw a simple line... it's not even a curve, let alone a great circle or a transverse mercator or anything fancy... just a simple bluddy line!?!?!? ;-)

A BIG Thank You to everyone who posted!

Cheers again. Keith.

like image 755
corlettk Avatar asked Sep 23 '12 06:09

corlettk


3 Answers

Calculate the vector AB

First define the vector from point A(1,-1) to point B(2,4) substracting A from B. The vector would be Vab(1,5).

Calculate the length of AB

Use Pythagorean theorem to calculate the length of vector AB.

|Vab| = SQRT(1²+5²)

The Length is (rounded) 5.1

Calculate the unit vector

Divide the vector by its length to get the unit vector (the vector with length 1).

V1(1/5.1,5/5.1) = V1(0.2, 0.98)

Calculate the vector with length 4

Now multiply V1 with the length you want, for example 4, to get Vt.

Vt(0.2*4,0.98*4) = Vt(0.8,3.92)

Calculate target point

Add the vector Vt to point A to get point T (target).

T = A + Vt = T(1.8,2.92)

EDIT: Answer to your edit

The method LengthOfHypotenuse should look like that

  • fixed an error on calculating bSq
  • and removed redundant Math.Abs call, because a pow of 2 is always positive
  • removed the addition of 0.5, don't know why you would need that
  • you should at least use a float as return value (double or decimal would work also)

    //You should work with Vector2 class instead of Point and use their Length property
    private double LengthOfHypotenuse(Point a, Point b) {
        double aSq = Math.Pow(a.X - b.X, 2); // horizontal length squared
        double bSq = Math.Pow(a.Y - b.Y, 2); // vertical length  squared
        return Math.Sqrt(aSq + bSq); // length of the hypotenuse
    }
    

The method Draw(Point a, Point b) should look like that:

  • Corrected DrawCell() call

    private void Draw(Point a, Point b) {
        double maxDistance = LengthOfHypotenuse(a, b);
        for (int distance = 0; distance < maxDistance; ++distance) {
            var point = CalculatePoint(new Vector2(a), new Vector2(b), distance);
            DrawCell(point.X, point.Y, _theLineBrush);
        }
    }
    

Your CalculatePoint(Point a, Point b, int distance) method:

  • Moved some calculations into Vector2 class

    private Point CalculatePoint(Vector2 a, Vector2 b, int distance) {
        Vector2 vectorAB = a - b;
    
        return a + vectorAB.UnitVector * distance;
    }
    

I have extended the Vector class for you to add the missing operators (credits to AgentFire)

    //AgentFire: Better approach (you can rename the struct if you need):
    struct Vector2 {
        public readonly double X;
        public readonly double Y;
        public Vector2(Point p) : this(p.X,p.Y) { 
        }

        public Vector2(double x, double y) {
            this.X = x;
            this.Y = y;
        }
        public static Vector2 operator -(Vector2 a, Vector2 b) {
            return new Vector2(b.X - a.X, b.Y - a.Y);
        }
        public static Vector2 operator +(Vector2 a, Vector2 b) {
            return new Vector2(b.X + a.X, b.Y + a.Y);
        }
        public static Vector2 operator *(Vector2 a, double d) {
            return new Vector2(a.X * d, a.Y * d);
        }
        public static Vector2 operator /(Vector2 a, double d) {
            return new Vector2(a.X / d, a.Y / d);
        }

        public static implicit operator Point(Vector2 a) {
            return new Point((int)a.X, (int)a.Y);
        }

        public Vector2 UnitVector {
            get { return this / Length; }
        }

        public double Length {
            get {
                double aSq = Math.Pow(X, 2);
                double bSq = Math.Pow(Y, 2);
                return Math.Sqrt(aSq + bSq);
            }
        }

        public override string ToString() {
            return string.Format("[{0}, {1}]", X, Y);
        }
    }
like image 159
dwonisch Avatar answered Oct 16 '22 08:10

dwonisch


Better approach (you can rename the struct if you need):

struct Vector2
{
    public readonly float X;
    public readonly float Y;

    public Vector2(float x, float y)
    {
        this.X = x;
        this.Y = y;
    }

    public static Vector2 operator -(Vector2 a, Vector2 b)
    {
        return new Vector2(b.X - a.X, b.Y - a.Y);
    }
    public static Vector2 operator +(Vector2 a, Vector2 b)
    {
        return new Vector2(a.X + b.X, a.Y + b.Y);
    }
    public static Vector2 operator *(Vector2 a, float d)
    {
        return new Vector2(a.X * d, a.Y * d);
    }

    public override string ToString()
    {
        return string.Format("[{0}, {1}]", X, Y);
    }
}

For getting the midpoint you just need to do the (a - b) * d + a action:

class Program
{
    static void Main(string[] args)
    {
        Vector2 a = new Vector2(1, 1);
        Vector2 b = new Vector2(3, 1);
        float distance = 0.5f; // From 0.0 to 1.0.
        Vector2 c = (a - b) * distance + a;
        Console.WriteLine(c);
    }
}

This will give you the point:

50%

output:\> [2, 1]

All you need after that is to for(the distance; up toone; d += step) from 0.0 to 1.0 and draw your pixels.

like image 22
AgentFire Avatar answered Oct 16 '22 10:10

AgentFire


    private static Point CalculatePoint(Point a, Point b, int distance)
    {

        // a. calculate the vector from o to g:
        double vectorX = b.X - a.X;
        double vectorY = b.Y - a.Y;

        // b. calculate the proportion of hypotenuse
        double factor = distance / Math.Sqrt(vectorX * vectorX + vectorY * vectorY);

        // c. factor the lengths
        vectorX *= factor;
        vectorY *= factor;

        // d. calculate and Draw the new vector,
        return new Point((int)(a.X + vectorX), (int)(a.Y + vectorY));
    }
like image 4
Cadair Idris Avatar answered Oct 16 '22 10:10

Cadair Idris