要想让目标在指定的路径上运行,需要在路径轨迹上选择很多点,再通过插值方法插入数据点,当点足够多的时候,运行起来看着就是连续的运动。起始坐标和终点坐标对应路径总长。所以,直接给定路径值小车即可运行到指定坐标位置。
操作方法:在运行路径上选点,尽量在运行路径上选择足够多的点,然后保存点坐标,通过插值法将这些选择的坐标扩展很多个。设置路径总长,对应起始坐标和终点坐标。则计算出路径上每一个位置对应的坐标。
1、界面效果图
创建一个环形路径轨迹,创建两个小车,设置上客区站台、下客区站台 ,在初始情况下1#小车在上客区站台,2#小车在下客区站台。启动后1#车和2#车分别运行。
2、路径选点
电机选点按钮,然后在路径上需要的位置按鼠标右键即可选择点,点和点之间会形成红色的连线提示选择的路径。
选好以后点击保存
然后重新启动软件,重新启动以后再启动,小车就沿着刚刚选中的路径运行。
3、轨迹地图功能类封装
public class DrawMap
{
public List<Point> m_pointList;//声明点集合(屏幕坐标点)
public List<Point> m_screenPointList;//原来的屏幕坐标点集合
public List<double> xy_List, length_List, k_List;
public PictureBoxCar[] m_pictureArray;//声明
public int pointcount = 0;
public long RealRail_length = 2000;
public double lengthflag;//图像轨道长与实际轨道之比
public Form mapFrom;
public DrawMap()
{
m_pointList = new List<Point>();
m_screenPointList = new List<Point>();
xy_List = new List<double>();
k_List = new List<double>();
length_List = new List<double>();
ReadPoint();//读取原来的坐标点
DealList(pointcount);//建立轨道
}
public DrawMap(Form map, long Real_length)
{
m_pointList = new List<Point>();
m_screenPointList = new List<Point>();
xy_List = new List<double>();
k_List = new List<double>();
length_List = new List<double>();
RealRail_length = Real_length;
ReadPoint();//读取原来的坐标点
DealList(pointcount);//建立轨道
mapFrom = map;
m_pictureArray = new PictureBoxCar[20];
for (int i = 0; i < 20; i++)
{
m_pictureArray[i] = new PictureBoxCar(i, mapFrom);
}
}
public void DealList(int point_count)
{
int x1, x2, y1, y2;
double raillength = 0;
double ff = 0;
for (int i = 0; i < point_count; i++)
{
if (i == point_count - 1)
{
x1 = m_screenPointList[i].X;
y1 = m_screenPointList[i].Y;
x2 = m_screenPointList[0].X;
y2 = m_screenPointList[0].Y;
}
else
{
x1 = m_screenPointList[i].X;
y1 = m_screenPointList[i].Y;
x2 = m_screenPointList[i + 1].X;
y2 = m_screenPointList[i + 1].Y;
}
double xy = Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));//两点距离
double o = Math.Atan2((y2 - y1), (x2 - x1));//两点斜率
raillength = raillength + xy;
xy_List.Add(xy);
k_List.Add(o);
}
lengthflag = raillength / RealRail_length;//MAP轨道长度与实际轨道长度之比
for (int i = 0; i < point_count; i++)
{
if (i > 0)
{
length_List.Add(ff + (xy_List[i] * RealRail_length / raillength));
ff += xy_List[i] * RealRail_length / raillength;
}
else
{
length_List.Add(xy_List[i] * RealRail_length / raillength);
ff += xy_List[i] * RealRail_length / raillength;
}
}
}
delegate void DrawCarDelegate1(int carIndex, float location);
//渲染小车的方法(model 0画上去 1为擦除)
public void DrawCar1(int carID, float location)
{
int flag = 0;
double ff = 0;
List<Point> pointList = m_screenPointList;//定义的坐标点
List<double> k_list1 = k_List;//坐标直线的斜率
List<double> length_list1 = length_List;//模拟的每条线段的长度
if (mapFrom.InvokeRequired)
{
DrawCarDelegate1 d = new DrawCarDelegate1(DrawCar1);
mapFrom.Invoke(d, new object[] { carID, location });
}
else
{
for (int i = 0; i < length_list1.Count; i++)
{
if (location < length_list1[i])
{
flag = i;
break;
}
ff += length_list1[i];
}
if (flag == 0)
{
double x = pointList[flag].X + location * Math.Cos(k_list1[flag]) * lengthflag;
double y = pointList[flag].Y + location * Math.Sin(k_list1[flag]) * lengthflag;
// m_pictureArray[carID].Picturebox.Location = new Point((int)Math.Ceiling(x), (int)Math.Ceiling(y));
m_pictureArray[carID].Picturebox.Location = new Point((int)Math.Ceiling(x - 10), (int)Math.Ceiling(y - m_pictureArray[carID].Picturebox.Height / 2));
m_pictureArray[carID].Picturebox.Visible = true;
}
else
{
double x = pointList[flag].X + (location - length_list1[flag - 1]) * Math.Cos(k_list1[flag]) * lengthflag;
double y = pointList[flag].Y + (location - length_list1[flag - 1]) * Math.Sin(k_list1[flag]) * lengthflag;
m_pictureArray[carID].Picturebox.Location = new Point((int)Math.Ceiling(x - 10), (int)Math.Ceiling(y - m_pictureArray[carID].Picturebox.Height / 2));
m_pictureArray[carID].Picturebox.Visible = true;
}
}
}
public void Hide_Carpicture(int carID)//隐藏掉线车辆
{
mapFrom.Invoke(new Action(() => { m_pictureArray[carID].Picturebox.Visible = false; }));
}
public void SaveScreenPoint(List<Point> pointList)
{
FileStream fs2 = null; //声明文件流的对象
StreamWriter sw = null;
string writePath = Application.StartupPath + "\\ScreenPoint\\screenPointList.txt";
//创建文件流
fs2 = new FileStream(writePath, //文件路径4
FileMode.Create, //打开文件的方式
FileAccess.Write, //控制对文件的读写
FileShare.None); //控制其它进程对此文件的访问
//创建写入器
sw = new StreamWriter(fs2, Encoding.Default);
for (int i = 0; i < pointList.Count; i++)
{
sw.WriteLine(pointList[i].X.ToString() + "," + pointList[i].Y.ToString());
}
sw.Flush();
if (fs2 != null)
{
if (sw != null)
{
sw.Close();
}
fs2.Close();
}
}
public void savePoint(bool m_saveBool)
{
if (m_saveBool)
{
if (m_pointList.Count > 0)
{
MessageBoxButtons messButton = MessageBoxButtons.OKCancel;
DialogResult dr = MessageBox.Show("确定保存吗?", "保存提示", messButton);
if (dr == DialogResult.OK)
{
List<Point> screenPointList = m_screenPointList;
screenPointList.Clear();//先清除之前的屏幕坐标点集合1915413225
//将本地的点集合传递给管理类的屏幕坐标集合
for (int i = 0; i < m_pointList.Count; i++)
{
screenPointList.Add(m_pointList[i]);
}
SaveScreenPoint(m_pointList);
}
m_pointList.Clear();//移除list中所有元素
}
}
}
public void selectPoint(bool m_selectBool, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && m_selectBool)
{
int x = e.Location.X;//获取到的x坐标
int y = e.Location.Y;//获取到的y坐标
// if (m_pointList.Count < 20)//20个点
// {
m_pointList.Add(new Point(x, y));
// }
var fff = m_pointList.Count;
if (fff > 1)
{
Graphics g = mapFrom.CreateGraphics();
g.DrawLine(new Pen(Color.Red, 3), m_pointList[fff - 1], m_pointList[fff - 2]);
}
}
}
public void ReadPoint()
{
string path = Application.StartupPath + "\\ScreenPoint\\screenPointList.txt";
FileStream fs1 = null; //声明文件流的对象
FileStream fs2 = null; //声明文件流的对象
StreamReader sr = null; //声明读取器的对象
StreamWriter sw = null;
try
{
//创建文件流
fs1 = new FileStream(path, //文件路径
FileMode.Open, //打开文件的方式
FileAccess.ReadWrite, //控制对文件的读写
FileShare.None); //控制其它进程对此文件的访问
//创建读取器
sr = new StreamReader(fs1, //文件流对象
Encoding.Default); //字符编码
string str;
while ((str = sr.ReadLine()) != null)
{
string[] ss = str.Split(',');
int x = Convert.ToInt32(ss[0]);
int y = Convert.ToInt32(ss[1]);
m_screenPointList.Add(new Point(x, y));
pointcount++;// 6/19加入
}
}
catch (Exception ex)
{
if (ex is System.IO.FileNotFoundException)
{
string writePath = Application.StartupPath + "\\ScreenPoint\\screenPointList.txt";
//创建文件流
fs2 = new FileStream(writePath, //文件路径
FileMode.Create, //打开文件的方式
FileAccess.Write, //控制对文件的读写
FileShare.None); //控制其它进程对此文件的访问
//创建写入器
sw = new StreamWriter(fs2, Encoding.Default);
sw.WriteLine("100,100");
sw.WriteLine("200,100");
sw.WriteLine("200,200");
sw.WriteLine("100,200");
m_screenPointList.Add(new Point(100, 100));
m_screenPointList.Add(new Point(200, 100));
m_screenPointList.Add(new Point(200, 200));
m_screenPointList.Add(new Point(100, 200));
sw.Flush();
}
else
{
MessageBox.Show("文件操作异常:" + "坐标格式不正确");
}
}
finally
{
if (fs1 != null)
{
if (sr != null)
{
sr.Close(); //关闭读取器
}
fs1.Close(); //关闭文件流
}
if (fs2 != null)
{
if (sw != null)
{
sw.Close();
}
fs2.Close();
}
}
}
}
4、小车绘制图
public PictureBoxCar(int carID, Form handle)
{
//carID = carID+1;//1-5id
m_picturebox = new PictureBox();
bmp = new Bitmap(65, 20);
m_g = Graphics.FromImage(bmp);
m_picturebox.Width = 65;
m_picturebox.Height = 20;
m_picturebox.BackColor = Color.Transparent;
m_g.FillEllipse(new SolidBrush(Color.Blue), new System.Drawing.Rectangle(0, 0, 20, 20));//擦除作用
m_g.DrawString("车" + carID.ToString(), new Font("宋体", 12), new SolidBrush(Color.Black), new System.Drawing.Rectangle(20, 3, 55, 20));
m_picturebox.Image = bmp;
m_picturebox.Parent = handle;
m_picturebox.Visible = false;
}
5、运动路径轨迹也可以是旋转的圆形
6、测试工程下载