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.
我要用疯狂的方法来计算给定直线a - b的点,在给定的距离a上,这样我就可以在两个给定的点之间画出直线。一开始听起来很简单,但我似乎做不到。更糟糕的是,我不明白我哪里出了问题。几何学(和数学)不是我的强项。
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.
我也读过类似的问题。事实上我解除我的当前实现CalculatePoint函数直接从马德斯Elvheim回答:给定一个开始和结束点,和一个距离,计算一个点在一条直线上(在后面的评论+修正-如果我正确地理解他)因为我独立尝试解决这个问题没有得到我,除了frusterpationland的头等快车票。
Here's my UPDATED code (please see the EDIT notes a bottom of post):
这是我更新后的代码(请参阅编辑注释a底部):
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://*.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.
不好意思,如果有点长,但是这个是SSCCE从我的真实项目中挖掘出来的,这是一个运行MazeOfBolton的最短路由算法的实现…即一个迷宫流道。
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.
我真的想做的是计算一个后面“栅栏”(即一个缓冲MBR)在两个给定的点(源和目标)迷宫(矩阵),这样“栅栏”内的所有点在给定距离“两点之间的直线”,为了快速消除可能的路径的成百上千朝远离目标。
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.
请注意,这个编程挑战在几年前就已经结束了,所以这里没有“竞争plag”的问题。不,这不是家庭作业,事实上我是一个专业的程序员……我只是在我的舒适区之外,即使是相对简单的几何学。叹息。
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?
所以…请大家给我一些指点,帮助我正确地计算出计算点的函数:在给定距离a的距离上计算直线a - b的点。
Thanks in advance for your generosity... even in reading this far.
预先感谢您的慷慨……甚至在阅读这篇文章的时候。
Cheers. Keith.
欢呼。基斯。
EDIT: I just updated the posted source code becuase:
编辑:我刚刚更新了发布的源代码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.
(1)我刚意识到它不是自我控制的。我忘记了seperate MainForm.Designer。cs文件,我已经把它添加到已发布代码的底部。
(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?
(2)最新版本包含了我迄今为止尝试过的内容,用photobucket链接到每个失败的图片。它们都是一样的。驾车吗?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 ;-)
我想我的问题可能在别的地方,就像以前被其他人错过的一些有趣的windows窗体设置,因为我忘记了发布设计器生成的代码……除了所有其他的东西(在我的实际项目中),在我期望它的地方绘制,那么为什么计算点应该是不同的。我不知道! ? ! ? ! ?我很沮丧,我变得暴躁了,所以我想我要把这个留到另一天去做。
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.
欢呼了。基斯。
6 个解决方案
#1
21
Calculate the vector AB
计算向量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).
首先定义向量从点A(1,-1)到点B(2,4)减去向量A,向量是Vab(1,5)。
Calculate the length of AB
计算AB的长度。
Use Pythagorean theorem to calculate the length of vector AB.
用勾股定理计算向量AB的长度。
|Vab| = SQRT(1²+5²)
The Length is (rounded) 5.1
长度为(圆形)5.1。
Calculate the unit vector
计算单位向量
Divide the vector by its length to get the unit vector (the vector with length 1).
将向量除以它的长度,得到单位向量(长度为1的向量)。
V1(1/5.1,5/5.1) = V1(0.2, 0.98)
Calculate the vector with length 4
计算长度为4的向量。
Now multiply V1 with the length you want, for example 4, to get Vt.
现在把V1和你想要的长度相乘,例如4,得到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).
将向量Vt加到点A以得到点T(目标)。
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
- 修正了计算bSq的错误。
- and removed redundant Math.Abs call, because a pow of 2 is always positive
- 和删除冗余的数学。Abs的调用,因为2的pow总是正的。
- removed the addition of 0.5, don't know why you would need that
- 去掉了0。5的加法,不知道你为什么需要它。
-
you should at least use a float as return value (double or decimal would work also)
您至少应该使用浮点数作为返回值(double或decimal也可以工作)
//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:
方法绘制(点a,点b)应该是这样的:
-
Corrected DrawCell() call
纠正DrawCell()调用
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:
你的CalculatePoint(点a,点b, int距离)方法:
-
Moved some calculations into Vector2 class
将一些计算移到Vector2类中。
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)
我已经扩展了Vector类,让您添加丢失的操作符(对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);
}
}
#2
5
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:
为了得到中点,你只需要做(a - b) * d +一个动作:
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:
这将给你一个要点:
output:\> [2, 1]
输出:\ >(2,1)
All you need after that is to for(the distance; up to
one
; d += step)
from 0.0 to 1.0 and draw your pixels.
你所需要的是(距离;积聚;从0.0到1.0,并绘制像素。
#3
4
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));
}
#4
2
Try to remove 'magnitude' term in the parentheses both for X and for Y expressions:
试着在X和Y表达式的括号中去掉“大小”项:
(int)( ((double)a.X + vectorX * distance) + 0.5 )
#5
1
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;
}
#6
1
OK guys, I found my major bug. It was a classic Doh! My Draw method was painting at p.X, p.X
好了,伙计们,我发现了我的主要错误。这是经典的Doh!我的画法是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" ;-)
编辑-更新以上代码:此版本绘制“实线”。之前发布的版本在几乎垂直的直线上跳过了单元格,所以我把算法倒转到在这些情况下计算X值(而不是Y值)……现在我可以用它在一个“可航行区域”周围设置(并绘制)一个“坚固的围栏”;
Here's an UPDATED picture of the correct results.
这是正确结果的更新图片。
Thanks again to everybody who helped... and you did help ;-)
再次感谢所有帮助过我的人…你确实帮了我的忙;
Cheers. Keith.
欢呼。基斯。
#1
21
Calculate the vector AB
计算向量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).
首先定义向量从点A(1,-1)到点B(2,4)减去向量A,向量是Vab(1,5)。
Calculate the length of AB
计算AB的长度。
Use Pythagorean theorem to calculate the length of vector AB.
用勾股定理计算向量AB的长度。
|Vab| = SQRT(1²+5²)
The Length is (rounded) 5.1
长度为(圆形)5.1。
Calculate the unit vector
计算单位向量
Divide the vector by its length to get the unit vector (the vector with length 1).
将向量除以它的长度,得到单位向量(长度为1的向量)。
V1(1/5.1,5/5.1) = V1(0.2, 0.98)
Calculate the vector with length 4
计算长度为4的向量。
Now multiply V1 with the length you want, for example 4, to get Vt.
现在把V1和你想要的长度相乘,例如4,得到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).
将向量Vt加到点A以得到点T(目标)。
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
- 修正了计算bSq的错误。
- and removed redundant Math.Abs call, because a pow of 2 is always positive
- 和删除冗余的数学。Abs的调用,因为2的pow总是正的。
- removed the addition of 0.5, don't know why you would need that
- 去掉了0。5的加法,不知道你为什么需要它。
-
you should at least use a float as return value (double or decimal would work also)
您至少应该使用浮点数作为返回值(double或decimal也可以工作)
//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:
方法绘制(点a,点b)应该是这样的:
-
Corrected DrawCell() call
纠正DrawCell()调用
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:
你的CalculatePoint(点a,点b, int距离)方法:
-
Moved some calculations into Vector2 class
将一些计算移到Vector2类中。
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)
我已经扩展了Vector类,让您添加丢失的操作符(对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);
}
}
#2
5
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:
为了得到中点,你只需要做(a - b) * d +一个动作:
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:
这将给你一个要点:
output:\> [2, 1]
输出:\ >(2,1)
All you need after that is to for(the distance; up to
one
; d += step)
from 0.0 to 1.0 and draw your pixels.
你所需要的是(距离;积聚;从0.0到1.0,并绘制像素。
#3
4
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));
}
#4
2
Try to remove 'magnitude' term in the parentheses both for X and for Y expressions:
试着在X和Y表达式的括号中去掉“大小”项:
(int)( ((double)a.X + vectorX * distance) + 0.5 )
#5
1
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;
}
#6
1
OK guys, I found my major bug. It was a classic Doh! My Draw method was painting at p.X, p.X
好了,伙计们,我发现了我的主要错误。这是经典的Doh!我的画法是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" ;-)
编辑-更新以上代码:此版本绘制“实线”。之前发布的版本在几乎垂直的直线上跳过了单元格,所以我把算法倒转到在这些情况下计算X值(而不是Y值)……现在我可以用它在一个“可航行区域”周围设置(并绘制)一个“坚固的围栏”;
Here's an UPDATED picture of the correct results.
这是正确结果的更新图片。
Thanks again to everybody who helped... and you did help ;-)
再次感谢所有帮助过我的人…你确实帮了我的忙;
Cheers. Keith.
欢呼。基斯。