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

前端 未结 6 1365
情歌与酒
情歌与酒 2020-11-29 08:54

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

相关标签:
6条回答
  • 2020-11-29 09:13
        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));
        }
    
    0 讨论(0)
  • 2020-11-29 09:13
    private Point CalculatePoint(Point a, Point b, int distance) {
          Point newPoint = new Point(10,10);
          Double Magnitude = Math.Sqrt(Math.Pow((b.Y - a.Y),2) + Math.Pow((b.X - a.X),2));
          newPoint.X = (int)(a.X + (distance * ((b.X - a.X)/magnitude)));
          newPoint.Y = (int)(a.Y + (distance * ((b.Y - a.Y)/magnitude)));
          return newPoint;
    }
    
    0 讨论(0)
  • 2020-11-29 09:20

    Try to remove 'magnitude' term in the parentheses both for X and for Y expressions:

    (int)(  ((double)a.X + vectorX * distance)  +  0.5  )
    
    0 讨论(0)
  • 2020-11-29 09:27

    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);
            }
        }
    
    0 讨论(0)
  • 2020-11-29 09:29

    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.

    0 讨论(0)
  • 2020-11-29 09:36

    OK guys, I found my major bug. It was a classic Doh! My Draw method was painting at p.X, p.X

    So, I finally got something that works. Please note that I am not saying that this a "good solution", or "the only working solution" I'm just saying that it does what I want it to do ;-)

    Here's my UPDATED working code: (complete and selfcontained this time ;-)

    using System;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Diagnostics;
    
    namespace DrawLines
    {
        public class MainForm : Form
        {
            #region constants and readonly attributes
    
            private const int CELL_SIZE = 4; // width and height of each "cell" in the bitmap.
    
            private readonly Bitmap _myBitmap; // to draw on (displayed in picBox1).
            private readonly Graphics _myGraphics; // to draw with.
    
            // actual points on _theLineString are painted red.
            private static readonly SolidBrush _thePointBrush = new SolidBrush(Color.Red);
            // ... and are labeled in /*Bold*/ Black, 16 point Courier New
            private static readonly SolidBrush _theLabelBrush = new SolidBrush(Color.Black);
            private static readonly Font _theLabelFont = new Font("Courier New", 16); //, FontStyle.Bold);
    
            // the interveening calculated cells on the lines between actaul points are painted Silver.
            private static readonly SolidBrush _theLineBrush = new SolidBrush(Color.Silver);
    
            // the points in my line-string.
            private static readonly Point[] _thePoints = new Point[] {
                //          x,   y      c i
                new Point(170,  85), // A 0 
                new Point( 85,  70), // B 1
                new Point(209,  66), // C 2
                new Point( 98, 120), // D 3
                new Point(158,  19), // E 4
                new Point(  2,  61), // F 5
                new Point( 42, 177), // G 6
                new Point(191, 146), // H 7
                new Point( 25, 128), // I 8
                new Point( 95,  24)  // J 9
            };
    
            #endregion
    
            public MainForm() {
                InitializeComponent();
                // initialise "the graphics system".
                _myBitmap = new Bitmap(picBox1.Width, picBox1.Height);
                _myGraphics = Graphics.FromImage(_myBitmap);
                picBox1.Image = _myBitmap;
            }
    
            #region DrawPoints upon MainForm_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 _thePoints ) {
                    DrawCell(p.X, p.Y, _thePointBrush);
                    DrawLabel(p.X, p.Y, c++);
                }
            }
    
            #endregion
    
            #region DrawLines on button click
    
            // =====================================================================
            // Here's the interesting bit. DrawLine was called Draw
    
            // Draws a line from A to B, by using X-values to calculate the Y values.
            private void DrawLine(Point a, Point b)
            {
                if ( a.Y > b.Y ) // A is below B
                    Swap(ref a, ref b); // make A the topmost point (ergo sort by Y)
                Debug.Assert(a.Y < b.Y, "A is still below B!");
    
                var left = Math.Min(a.X, b.X);
                var right = Math.Max(a.X, b.X);
                int width = right - left;
                Debug.Assert(width >= 0, "width is negative!");
    
                var top = a.Y;
                var bottom = b.Y;
                int height = bottom - top;
                Debug.Assert(height >= 0, "height is negative!");
    
                if ( width > height ) {
                    // use given X values to calculate the Y values, 
                    // otherwise it "skips" some X's
                    double slope = (double)height / (double)width; 
                    Debug.Assert(slope >= 0, "slope is negative!");
                    if (a.X <= b.X)     // a is left-of b, so draw left-to-right.
                        for ( int x=1; x<width; ++x ) // xOffset
                            DrawCell( (left+x), (a.Y + ((int)(slope*x + 0.5))), _theLineBrush);
                    else                // a is right-of b, so draw right-to-left.
                        for ( int x=1; x<width; ++x ) // xOffset
                            DrawCell( (right-x), (a.Y + ((int)(slope*x + 0.5))), _theLineBrush);
                } else {
                    // use given Y values to calculate the X values, 
                    // otherwise it "skips" some Y's
                    double slope = (double)width/ (double)height; 
                    Debug.Assert(slope >= 0, "slope is negative!");
                    if (a.X <= b.X) {     // a is left-of b, so draw left-to-right. (FG)
                        for ( int y=1; y<height; ++y ) // yOffset
                            DrawCell( (a.X + ((int)(slope*y + 0.5))), (top+y), _theLineBrush);
                    } else {              // a is right-of b, so draw right-to-left. (DE,IJ)
                        for ( int y=1; y<height; ++y ) // yOffset
                            DrawCell( (b.X + ((int)(slope*y + 0.5))), (bottom-y), _theLineBrush);
                    }
                }
            }
    
            private void btnDrawLines_Click(object sender, EventArgs e) {
                DrawLines();  // join the points
                DrawPoints(); // redraw the labels over the lines.
            }
    
            // Draws a line between each point in _theLineString.
            private void DrawLines() {
                int n = _thePoints.Length - 1; // one less line-segment than points
                for ( int i=0; i<n; ++i )
                    DrawLine(_thePoints[i], _thePoints[i+1]);
                picBox1.Invalidate(); // tell the graphics system that the picture box needs to be repainted.
            }
    
            private void Swap(ref Point a, ref Point b) {
                Point tmp = a;
                a = b;
                b = tmp;
            }
    
            #endregion
    
            #region actual drawing on _myGraphics
    
            // there should be no calls to Draw or Fill outside of this region
    
            private void DrawCell(int x, int y, Brush brush) {
                _myGraphics.FillRectangle(
                    brush
                  , x*CELL_SIZE
                  , y*CELL_SIZE 
                  , CELL_SIZE   // width
                  , CELL_SIZE   // heigth
                );
            }
    
            private void DrawLabel(int x, int y, char c) {
                string s = c.ToString();
                _myGraphics.DrawString(
                    s, _theLabelFont, _theLabelBrush
                  , x * CELL_SIZE + 5   // x
                  , y * CELL_SIZE - 10  // y
                );
            }
    
            #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
        }
    
    }
    

    EDIT - UPDATED ABOVE CODE: This version draws "solid" lines. The previously posted version skipped cells in nearly vertical lines, so I inverted the algorithm to calculate the X value (instead of the Y value) in these cases... now I can use it to set (and draw) a "solid fence" around a "navigable area" ;-)

    Here's an UPDATED picture of the correct results.

    DrawLiness_solid_success.png

    Thanks again to everybody who helped... and you did help ;-)

    Cheers. Keith.

    0 讨论(0)
提交回复
热议问题