Example #1
0
        public RobotPath(RobotPath path)
        {
            m_segments = null;
            m_splines  = null;

            Name                  = path.Name;
            MaxVelocity           = path.MaxVelocity;
            MaxAcceleration       = path.MaxAcceleration;
            MaxJerk               = path.MaxJerk;
            StartVelocity         = path.StartVelocity;
            EndVelocity           = path.EndVelocity;
            StartFacingAngle      = path.StartFacingAngle;
            EndFacingAngle        = path.EndFacingAngle;
            FacingAngleStartDelay = path.FacingAngleStartDelay;
            FacingAngleEndDelay   = path.FacingAngleEndDelay;
            Points                = new WayPoint[path.Points.Length];
            for (int i = 0; i < path.Points.Length; i++)
            {
                Points[i] = new WayPoint(path.Points[i]);
            }

            Constraints = new TimingContraint[path.Constraints.Length];
            for (int i = 0; i < path.Constraints.Length; i++)
            {
                Constraints[i] = path.Constraints[i].Clone();
            }
            m_lock = new object();
        }
Example #2
0
        private void GenerateSegmentsForPath(RobotParams robot, RobotPath path)
        {
            PathSegment[] segs = null;
            Dictionary <string, PathSegment[]> add = null; DriveModifier mod = null;
            double rotpercent = 0.0;
            bool   running    = true;

            if (robot.DriveType == RobotParams.TankDriveType)
            {
                mod = new TankDriveModifier();
            }
            else if (robot.DriveType == RobotParams.SwerveDriveType)
            {
                mod = new SwerveDriveModifier();
            }

            while (running)
            {
                RobotParams nrobot = new RobotParams(robot);
                nrobot.MaxVelocity = (1 - rotpercent) * robot.MaxVelocity;

                segs = GenerateDetailedPath(nrobot, path);
                try
                {
                    add     = mod.ModifyPath(robot, path, segs, rotpercent * robot.MaxVelocity);
                    running = false;
                }
                catch (DriveModifier.VelocitySplitException)
                {
                    rotpercent += 0.05;
                }
            }
            path.SetSegments(segs, add);
        }
Example #3
0
        protected override void OnMouseUp(MouseEventArgs e)
        {
            PathGroup gr   = null;
            RobotPath path = null;

            base.OnMouseUp(e);

            Capture = false;
            if (m_dragging)
            {
                m_file.FindPathByWaypoint(m_selected, out gr, out path);
                m_dragging = false;
                if (!m_last_msg_selected)
                {
                    m_file.FindPathByWaypoint(m_selected, out gr, out path);
                    OnWayPointChanged(new WaypointEventArgs(WaypointEventArgs.ReasonType.EndChange, gr, path, m_selected));
                }
                Invalidate();
            }
            else if (m_rotating != null)
            {
                m_selected = m_rotating;
                m_rotating = null;

                if (!m_last_msg_selected)
                {
                    m_file.FindPathByWaypoint(m_selected, out gr, out path);
                    OnWayPointChanged(new WaypointEventArgs(WaypointEventArgs.ReasonType.EndChange, gr, path, m_selected));
                }
                Invalidate();
            }
        }
Example #4
0
        public bool FindPathByWaypoint(WayPoint pt, out PathGroup group, out RobotPath path, out int index)
        {
            group = null;
            path  = null;
            index = -1;

            foreach (PathGroup gr in Groups)
            {
                foreach (RobotPath pa in gr.Paths)
                {
                    for (int i = 0; i < pa.Points.Length; i++)
                    {
                        if (pa.Points[i] == pt)
                        {
                            group = gr;
                            path  = pa;
                            index = i;

                            return(true);
                        }
                    }
                }
            }

            return(false);
        }
Example #5
0
        public void RemovePath(string name)
        {
            int index = -1;

            for (int i = 0; i < Paths.Length; i++)
            {
                if (Paths[i].Name == name)
                {
                    index = i;
                    break;
                }
            }

            if (index != -1)
            {
                RobotPath[] temp = new RobotPath[Paths.Length - 1];
                if (index > 0)
                {
                    Array.Copy(Paths, 0, temp, 0, index);
                }

                if (index < Paths.Length - 1)
                {
                    Array.Copy(Paths, index + 1, temp, index, Paths.Length - index - 1);
                }

                Paths = temp;
            }
        }
Example #6
0
 private void DrawSplines(Graphics g, RobotPath path)
 {
     for (int i = 0; i < path.Splines.Length; i++)
     {
         DrawSpline(g, path, i);
     }
 }
Example #7
0
        public static void Diff(PathGroup p1, PathGroup p2, List <string> diff)
        {
            List <PathToCompare> todiff = new List <PathToCompare>();

            foreach (RobotPath path in p1.Paths)
            {
                RobotPath second = Array.Find(p2.Paths, search => search.Name == path.Name);
                if (second == null)
                {
                    diff.Add("p2 missing path in group '" + p1.Name + "', path '" + path.Name + "'");
                }
                else
                {
                    PathToCompare pcmp = new PathToCompare(p1, path, p2, second);
                    todiff.Add(pcmp);
                }
            }

            foreach (RobotPath path in p2.Paths)
            {
                RobotPath first = Array.Find(p1.Paths, search => search.Name == path.Name);
                if (first == null)
                {
                    diff.Add("p1 missing path in group '" + p2.Name + "', path '" + path.Name + "'");
                }
            }

            foreach (PathToCompare p in todiff)
            {
                Diff(p.PathGroupOne, p.PathOne, p.PathGroupTwo, p.PathTwo, diff);
            }
        }
Example #8
0
 public PathToCompare(PathGroup pg1, RobotPath p1, PathGroup pg2, RobotPath p2)
 {
     PathGroupOne = pg1;
     PathOne      = p1;
     PathGroupTwo = pg2;
     PathTwo      = p2;
 }
Example #9
0
        public BasicFieldView()
        {
            m_path            = null;
            m_world_to_window = new Matrix();
            m_window_to_world = new Matrix();
            DoubleBuffered    = true;

            InitializeComponent();
        }
Example #10
0
        private void DrawPath(Graphics g, RobotPath path)
        {
            if (path.HasSplines)
            {
                DrawSplines(g, path);
            }

            DrawPoints(g, path.Points);
        }
Example #11
0
        public PathGroup(PathGroup pg)
        {
            Name  = pg.Name;
            Paths = new RobotPath[pg.Paths.Length];

            for (int i = 0; i < Paths.Length; i++)
            {
                Paths[i] = new RobotPath(pg.Paths[i]);
            }
        }
Example #12
0
        public bool RenamePath(string oldname, string newname)
        {
            RobotPath pt = FindPathByName(oldname);

            if (pt == null)
            {
                return(false);
            }

            pt.Name = newname;
            return(true);
        }
Example #13
0
        public void AddPath(string group, RobotPath path)
        {
            PathGroup gr = FindGroupByName(group);

            if (gr == null)
            {
                return;
            }

            m_dirty = true;
            gr.AddPath(path);
        }
Example #14
0
        public bool Contains(RobotPath path)
        {
            foreach (PathGroup group in Groups)
            {
                if (group.Contains(path))
                {
                    return(true);
                }
            }

            return(false);
        }
Example #15
0
        public void GenerateSegments(RobotParams robot, RobotPath path)
        {
            PathGenerationStateChangeEvent args  = null;
            Tuple <RobotParams, RobotPath> entry = new Tuple <RobotParams, RobotPath>(robot, path);

            lock (m_lock)
            {
                m_queue.Add(entry);
                int running = m_active ? 1 : 0;
                int queued  = m_queue.Count;
                args = new PathGenerationStateChangeEvent(running, queued);
            }
            OnJobStateChanged(args);
        }
Example #16
0
        public PathGroup FindGroupByPath(RobotPath p)
        {
            foreach (PathGroup gr in Groups)
            {
                foreach (RobotPath pa in gr.Paths)
                {
                    if (pa == p)
                    {
                        return(gr);
                    }
                }
            }

            return(null);
        }
Example #17
0
        public void AddPath(RobotParams robot, string name)
        {
            RobotPath path = new RobotPath(name);

            path.MaxVelocity     = robot.MaxVelocity;
            path.MaxAcceleration = robot.MaxAcceleration;
            path.MaxJerk         = robot.MaxJerk;

            Array.Resize <RobotPath>(ref Paths, Paths.Length + 1);
            Paths[Paths.Length - 1] = path;

            double length = UnitConverter.Convert(100.0, "inches", robot.LengthUnits);

            path.AddPoint(new WayPoint(0.0, 0.0, 0.0, 0.0));
            path.AddPoint(new WayPoint(length, 0.0, 0.0, 0.0));
        }
Example #18
0
        private void SetPath(RobotPath path)
        {
            if (m_path != null)
            {
                m_path.SegmentsUpdated -= PathSegmentsUpdated;
            }

            m_path = path;
            if (m_path != null)
            {
                m_path.SegmentsUpdated += PathSegmentsUpdated;
                PathSegmentsUpdated(null, null);
                Time = 0.0;
            }
            else
            {
                m_chart.Series.Clear();
            }
        }
Example #19
0
        private void DrawSpline(Graphics g, RobotPath path, int index)
        {
            float diam = 2.0f;

            using (Brush b = new SolidBrush(Color.LightCoral))
            {
                double step = 1.0 / 1000.0;
                for (double t = 0.0; t < 1.0; t += step)
                {
                    double     x, y, heading;
                    RectangleF rf;
                    double     px, py;
                    PointF     pt, w;

                    path.Evaluate(index, t, out x, out y, out heading);
                    heading = XeroUtils.DegreesToRadians(heading);
                    double ca = Math.Cos(heading);
                    double sa = Math.Sin(heading);

                    px = x - m_file.Robot.Width * sa / 2.0;
                    py = y + m_file.Robot.Width * ca / 2.0;

                    pt = new PointF((float)px, (float)py);
                    w  = WorldToWindow(pt);

                    rf = new RectangleF(w, new SizeF(0.0f, 0.0f));
                    rf.Offset(diam / 2.0f, diam / 2.0f);
                    rf.Inflate(diam, diam);
                    g.FillEllipse(b, rf);

                    px = x + m_file.Robot.Width * sa / 2.0;
                    py = y - m_file.Robot.Width * ca / 2.0;

                    pt = new PointF((float)px, (float)py);
                    w  = WorldToWindow(pt);

                    rf = new RectangleF(w, new SizeF(0.0f, 0.0f));
                    rf.Offset(diam / 2.0f, diam / 2.0f);
                    rf.Inflate(diam, diam);
                    g.FillEllipse(b, rf);
                }
            }
        }
Example #20
0
        protected bool GeneratePathFile(RobotParams robot, RobotPath path, string filename)
        {
            bool     ret = true;
            PathFile pf  = new PathFile();

            pf.Robot = robot;
            pf.AddPathGroup("tmp");
            pf.AddPath("tmp", path);

            string json = JsonConvert.SerializeObject(pf);

            try
            {
                File.WriteAllText(filename, json);
            }
            catch (Exception)
            {
                ret = false;
            }

            return(ret);
        }
Example #21
0
        public bool FindPathByWaypoint(WayPoint pt, out PathGroup group, out RobotPath path)
        {
            group = null;
            path  = null;

            foreach (PathGroup gr in Groups)
            {
                foreach (RobotPath pa in gr.Paths)
                {
                    foreach (WayPoint wy in pa.Points)
                    {
                        if (wy == pt)
                        {
                            group = gr;
                            path  = pa;
                            return(true);
                        }
                    }
                }
            }

            return(false);
        }
Example #22
0
 public NoSegmentsException(RobotPath path)
 {
     Path = path;
 }
Example #23
0
        public override Dictionary <string, PathSegment[]> ModifyPath(RobotParams robot, RobotPath path, PathSegment[] segs, double rotvel)
        {
            Dictionary <string, PathSegment[]> result = new Dictionary <string, PathSegment[]>();

            if (segs == null)
            {
                return(null);
            }

            double lvel, lacc, ljerk, lpos = 0.0;
            double rvel, racc, rjerk, rpos = 0.0;
            double plx = 0.0, ply = 0.0;
            double prx = 0.0, pry = 0.0;
            double plvel = 0.0, prvel = 0.0;
            double placc = 0.0, pracc = 0.0;

            PathSegment[] lsegs = new PathSegment[segs.Length];
            PathSegment[] rsegs = new PathSegment[segs.Length];

            result["left"]  = lsegs;
            result["right"] = rsegs;

            for (int i = 0; i < segs.Length; i++)
            {
                double time    = segs[i].GetValue("time");
                double heading = XeroUtils.DegreesToRadians(segs[i].GetValue("heading"));
                double ca      = Math.Cos(heading);
                double sa      = Math.Sin(heading);

                double px = segs[i].GetValue("x");
                double py = segs[i].GetValue("y");

                double lx = px - robot.Width * sa / 2.0;
                double ly = py + robot.Width * ca / 2.0;
                double rx = px + robot.Width * sa / 2.0;
                double ry = py - robot.Width * ca / 2.0;

                if (i == 0)
                {
                    lvel  = 0.0;
                    lacc  = 0.0;
                    lpos  = 0.0;
                    ljerk = 0.0;

                    rvel  = 0.0;
                    racc  = 0.0;
                    rpos  = 0.0;
                    rjerk = 0.0;
                }
                else
                {
                    double dt    = segs[i].GetValue("time") - segs[i - 1].GetValue("time");
                    double ldist = Math.Sqrt((lx - plx) * (lx - plx) + (ly - ply) * (ly - ply));
                    double rdist = Math.Sqrt((rx - prx) * (rx - prx) + (ry - pry) * (ry - pry));

                    lvel = ldist / dt;
                    rvel = rdist / dt;

                    lacc = (lvel - plvel) / dt;
                    racc = (rvel - prvel) / dt;

                    ljerk = (lacc - placc) / dt;
                    rjerk = (racc - pracc) / dt;

                    lpos += ldist;
                    rpos += rdist;
                }

                PathSegment left = new PathSegment();
                left.SetValue("time", time);
                left.SetValue("x", lx);
                left.SetValue("y", ly);
                left.SetValue("heading", segs[i].GetValue("heading"));
                left.SetValue("position", lpos);
                left.SetValue("velocity", lvel);
                left.SetValue("acceleration", lacc);
                left.SetValue("jerk", ljerk);
                lsegs[i] = left;

                PathSegment right = new PathSegment();
                right.SetValue("time", segs[i].GetValue("time"));
                right.SetValue("x", rx);
                right.SetValue("y", ry);
                right.SetValue("heading", segs[i].GetValue("heading"));
                right.SetValue("position", rpos);
                right.SetValue("velocity", rvel);
                right.SetValue("acceleration", racc);
                right.SetValue("jerk", rjerk);
                rsegs[i] = right;

                plx   = lx;
                ply   = ly;
                prx   = rx;
                pry   = ry;
                plvel = lvel;
                prvel = rvel;
                placc = lacc;
                pracc = racc;
            }

            return(result);
        }
Example #24
0
 public abstract Dictionary <string, PathSegment[]> ModifyPath(RobotParams robot, RobotPath path, PathSegment[] segs, double rotvel);
Example #25
0
        public override Dictionary <string, PathSegment[]> ModifyPath(RobotParams robot, RobotPath path, PathSegment[] segs, double rotvel)
        {
            Dictionary <string, PathSegment[]> result = new Dictionary <string, PathSegment[]>();

            if (segs == null)
            {
                return(null);
            }

            double             total   = segs[segs.Length - 1].GetValue("time");
            double             rtime   = total - path.FacingAngleStartDelay - path.FacingAngleEndDelay;
            TrapezoidalProfile profile = CreateRotationProfile(robot, path.StartFacingAngle, path.EndFacingAngle, rtime, rotvel);

            if (profile == null)
            {
                throw new DriveModifier.VelocitySplitException();
            }

            PathSegment[] fl = new PathSegment[segs.Length];
            PathSegment[] fr = new PathSegment[segs.Length];
            PathSegment[] bl = new PathSegment[segs.Length];
            PathSegment[] br = new PathSegment[segs.Length];

            result["fl"] = fl;
            result["fr"] = fr;
            result["bl"] = bl;
            result["br"] = br;

            for (int i = 0; i < segs.Length; i++)
            {
                //
                // The time in the path
                //
                double time = segs[i].GetValue("time");

                //
                // The current facing angle based on the rotational trapezoidal profile
                //
                double current = XeroUtils.BoundDegrees(path.StartFacingAngle + profile.GetDistance(time - path.FacingAngleStartDelay));

                //
                // Get the required velocity to perform the rotation.  This is the
                // linear ground based velocity for the wheel that must be applied to
                // each wheel in direction perpendicular to the vector from the center of
                // the robot to the center of the wheel.
                //
                double rv = RotationalToGround(robot, profile.GetVelocity(time - path.FacingAngleStartDelay));
                double ra = RotationalToGround(robot, profile.GetAcceleration(time - path.FacingAngleStartDelay));

                //
                // Get the velocity vector and acceleration vector relative to each of the
                // wheels to rotate the robot based on the desired rotational velocity.
                //
                XeroVector rotflvel = GetWheelPerpendicularVector(robot, Wheel.FL, rv);
                XeroVector rotfrvel = GetWheelPerpendicularVector(robot, Wheel.FR, rv);
                XeroVector rotblvel = GetWheelPerpendicularVector(robot, Wheel.BL, rv);
                XeroVector rotbrvel = GetWheelPerpendicularVector(robot, Wheel.BR, rv);

                XeroVector rotflacc = GetWheelPerpendicularVector(robot, Wheel.FL, ra);
                XeroVector rotfracc = GetWheelPerpendicularVector(robot, Wheel.FR, ra);
                XeroVector rotblacc = GetWheelPerpendicularVector(robot, Wheel.BL, ra);
                XeroVector rotbracc = GetWheelPerpendicularVector(robot, Wheel.BR, ra);

                //
                // Get the translational velocity vector to follow the path
                //
                XeroVector pathvel = XeroVector.FromDegreesMagnitude(segs[i].GetValue("heading"), segs[i].GetValue("velocity")).RotateDegrees(-current);
                XeroVector pathacc = XeroVector.FromDegreesMagnitude(segs[i].GetValue("heading"), segs[i].GetValue("acceleration")).RotateDegrees(-current);



                //
                // For each wheel, the velocity vector is the sum of the rotational vector and the translational vector
                //
                XeroVector flv = rotflvel + pathvel;
                XeroVector frv = rotfrvel + pathvel;
                XeroVector blv = rotblvel + pathvel;
                XeroVector brv = rotbrvel + pathvel;
                XeroVector fla = rotflacc + pathvel;
                XeroVector fra = rotfracc + pathvel;
                XeroVector bla = rotblacc + pathvel;
                XeroVector bra = rotbracc + pathvel;

                //
                // Now calculate the wheel positions based on the path position and the
                // facing angle
                //
                XeroVector flpos = new XeroVector(robot.Length / 2.0, robot.Width / 2.0).RotateDegrees(current).Translate(segs[i].GetValue("x"), segs[i].GetValue("y"));
                XeroVector frpos = new XeroVector(robot.Length / 2.0, -robot.Width / 2.0).RotateDegrees(current).Translate(segs[i].GetValue("x"), segs[i].GetValue("y"));
                XeroVector blpos = new XeroVector(-robot.Length / 2.0, robot.Width / 2.0).RotateDegrees(current).Translate(segs[i].GetValue("x"), segs[i].GetValue("y"));
                XeroVector brpos = new XeroVector(-robot.Length / 2.0, -robot.Width / 2.0).RotateDegrees(current).Translate(segs[i].GetValue("x"), segs[i].GetValue("y"));

                PathSegment newseg = new PathSegment(segs[i]);
                newseg.SetValue("x", flpos.X);
                newseg.SetValue("y", flpos.Y);
                newseg.SetValue("velocity", flv.Magnitude());
                newseg.SetValue("heading", flv.AngleDegrees());
                newseg.SetValue("acceleration", fla.Magnitude());
                fl[i] = newseg;

                newseg = new PathSegment(segs[i]);
                newseg.SetValue("x", frpos.X);
                newseg.SetValue("y", frpos.Y);
                newseg.SetValue("velocity", frv.Magnitude());
                newseg.SetValue("heading", frv.AngleDegrees());
                newseg.SetValue("acceleration", fra.Magnitude());
                fr[i] = newseg;

                newseg = new PathSegment(segs[i]);
                newseg.SetValue("x", blpos.X);
                newseg.SetValue("y", blpos.Y);
                newseg.SetValue("velocity", blv.Magnitude());
                newseg.SetValue("heading", blv.AngleDegrees());
                newseg.SetValue("acceleration", bla.Magnitude());
                bl[i] = newseg;

                newseg = new PathSegment(segs[i]);
                newseg.SetValue("x", brpos.X);
                newseg.SetValue("y", brpos.Y);
                newseg.SetValue("velocity", brv.Magnitude());
                newseg.SetValue("heading", brv.AngleDegrees());
                newseg.SetValue("acceleration", bra.Magnitude());
                br[i] = newseg;
            }

            return(result);
        }
Example #26
0
 public void SetSelectedPath(PathGroup group, RobotPath path)
 {
     m_selected_path = new Tuple <string, string>(group.Name, path.Name);
 }
Example #27
0
 public abstract PathSegment[] GenerateDetailedPath(RobotParams robot, RobotPath path);
Example #28
0
 public abstract void GenerateSplines(RobotPath path, List <Spline> xsplines, List <Spline> ysplines);
Example #29
0
        private void DrawPathRobot(Graphics g, RobotPath path)
        {
            if (path.AdditionalSegments == null)
            {
                return;
            }

            PointF[] pts = null;
            Dictionary <string, PathSegment[]> segs = path.AdditionalSegments;

            if (segs.Count == 2)
            {
                XeroPose left, right;
                left  = path.GetPositionForTime(segs["left"], m_time);
                right = path.GetPositionForTime(segs["right"], m_time);

                XeroPose center = left.Interpolate(right, 0.5);

                pts = new PointF[]
                {
                    new PointF((float)Robot.Length / 2.0f, -(float)Robot.Width / 2.0f),
                    new PointF(-(float)Robot.Length / 2.0f, -(float)Robot.Width / 2.0f),
                    new PointF(-(float)Robot.Length / 2.0f, (float)Robot.Width / 2.0f),
                    new PointF((float)Robot.Length / 2.0f, (float)Robot.Width / 2.0f),
                };

                Matrix mm = new Matrix();
                mm.Translate((float)center.X, (float)center.Y);
                mm.Rotate((float)center.HeadingDegrees);
                mm.TransformPoints(pts);
                WorldToWindowMatrix.TransformPoints(pts);
            }
            else
            {
                XeroPose fl, fr, bl, br;
                fl = path.GetPositionForTime(segs["fl"], m_time);
                fr = path.GetPositionForTime(segs["fr"], m_time);
                bl = path.GetPositionForTime(segs["bl"], m_time);
                br = path.GetPositionForTime(segs["br"], m_time);

                pts = new PointF[4]
                {
                    new PointF((float)fr.X, (float)fr.Y),
                    new PointF((float)br.X, (float)br.Y),
                    new PointF((float)bl.X, (float)bl.Y),
                    new PointF((float)fl.X, (float)fl.Y),
                };

                WorldToWindowMatrix.TransformPoints(pts);
            }

            using (Pen p = new Pen(Color.Yellow, 3.0f))
            {
                g.DrawLines(p, pts);
            }

            using (Pen p = new Pen(Color.Green, 3.0f))
            {
                g.DrawLine(p, pts[3], pts[0]);
            }
        }
Example #30
0
        public static void Diff(PathGroup pgr1, RobotPath p1, PathGroup pgr2, RobotPath p2, List <string> diff)
        {
            if (Math.Abs(p1.MaxVelocity - p2.MaxVelocity) > kEpsilon)
            {
                diff.Add("MaxVelocity: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.MaxVelocity.ToString() + ", p2 = " + p2.MaxVelocity.ToString());
            }

            if (Math.Abs(p1.MaxAcceleration - p2.MaxAcceleration) > kEpsilon)
            {
                diff.Add("MaxAcceleration: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.MaxAcceleration.ToString() + ", p2 = " + p2.MaxAcceleration.ToString());
            }

            if (Math.Abs(p1.MaxJerk - p2.MaxJerk) > kEpsilon)
            {
                diff.Add("MaxJerk: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.MaxJerk.ToString() + ", p2 = " + p2.MaxJerk.ToString());
            }

            if (Math.Abs(p1.StartVelocity - p2.StartVelocity) > kEpsilon)
            {
                diff.Add("StartVelocity: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.StartVelocity.ToString() + ", p2 = " + p2.StartVelocity.ToString());
            }

            if (Math.Abs(p1.EndVelocity - p2.EndVelocity) > kEpsilon)
            {
                diff.Add("EndVelocity: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.EndVelocity.ToString() + ", p2 = " + p2.EndVelocity.ToString());
            }

            if (Math.Abs(p1.StartFacingAngle - p2.StartFacingAngle) > kEpsilon)
            {
                diff.Add("StartFacingAngle: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.StartFacingAngle.ToString() + ", p2 = " + p2.StartFacingAngle.ToString());
            }

            if (Math.Abs(p1.EndFacingAngle - p2.EndFacingAngle) > kEpsilon)
            {
                diff.Add("EndFacingAngle: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.EndFacingAngle.ToString() + ", p2 = " + p2.EndFacingAngle.ToString());
            }

            if (Math.Abs(p1.FacingAngleStartDelay - p2.FacingAngleStartDelay) > kEpsilon)
            {
                diff.Add("FacingAngleStartDelay: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.FacingAngleStartDelay.ToString() + ", p2 = " + p2.FacingAngleStartDelay.ToString());
            }

            if (Math.Abs(p1.FacingAngleEndDelay - p2.FacingAngleEndDelay) > kEpsilon)
            {
                diff.Add("FacingAngleEndDelay: p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.FacingAngleEndDelay.ToString() + ", p2 = " + p2.FacingAngleEndDelay.ToString());
            }


            // Constraints

            // Waypoints
            if (p1.Points.Length != p2.Points.Length)
            {
                diff.Add("Waypoints: p1 has " + p1.Points.Length.ToString() + ", p2 has " + p2.Points.Length.ToString());
            }
            else
            {
                for (int i = 0; i < p1.Points.Length; i++)
                {
                    if (Math.Abs(p1.Points[i].X - p2.Points[i].X) > kEpsilon)
                    {
                        diff.Add("X: WayPoint " + i.ToString() + "p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.Points[i].X.ToString() + ", p2 = " + p2.Points[i].X.ToString());
                    }

                    if (Math.Abs(p1.Points[i].Y - p2.Points[i].Y) > kEpsilon)
                    {
                        diff.Add("Y: WayPoint " + i.ToString() + "p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.Points[i].Y.ToString() + ", p2 = " + p2.Points[i].Y.ToString());
                    }

                    if (Math.Abs(p1.Points[i].Heading - p2.Points[i].Heading) > kEpsilon)
                    {
                        diff.Add("Heading: WayPoint " + i.ToString() + "p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.Points[i].Heading.ToString() + ", p2 = " + p2.Points[i].Heading.ToString());
                    }

                    if (Math.Abs(p1.Points[i].Velocity - p2.Points[i].Velocity) > kEpsilon)
                    {
                        diff.Add("Velocity: WayPoint " + i.ToString() + "p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.Points[i].Velocity.ToString() + ", p2 = " + p2.Points[i].Velocity.ToString());
                    }

                    if (Math.Abs(p1.Points[i].Velocity - p2.Points[i].Velocity) > kEpsilon)
                    {
                        diff.Add("Velocity: WayPoint " + i.ToString() + "p1 = " + "PathGroup '" + pgr1.Name + "', Path '" + p1.Name + "'" + p1.Points[i].Velocity.ToString() + ", p2 = " + p2.Points[i].Velocity.ToString());
                    }
                }
            }
        }