I'm going quite mad tring 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¤t=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¤t=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.