3

I'm trying to create a very simple feature for a home-brew computer-aided-machining software application I'm working on. Basically, I've drawn out some tool-paths that will tell a milling machine where to travel. So, imagine I have 3 sets of line segments, each with say 100 individual line segments, each contained within its own List, as follows:

List<PointF> points = new List<PointF>();
List<PointF> pointsOffsetHigh = new List<PointF>();  
List<PointF> pointsOffsetLow = new List<PointF>(); 

Let's say they are criss-crossing one another on the screen, and I want each one to be treated as its own object when I click on any line segment within it. How would I approach that? I can already select an individual line segment using this excellent example from StackOverflow:Graphic - DrawLine - draw line and move it

Once I select a series of line segments, I'm going to see where it intersects with another series of line segments, and then erase one half of it. It's something very basic to any CAD program, but something that looks so simple on the screen has so much complexity behind it.

If anyone can help, I'd appreciate it. Code, general approach, I'll take anything.

Community
  • 1
  • 1
  • Have you searched about 'line intersection algorithm' ? – aybe Dec 28 '15 at 21:49
  • FYI, there are libraries that can save you the trouble of doing all the geometry data structures and algorithms yourself. I've had success with NetTopologySuite (a port of the Java library JTS), which is designed for GIS (Geographical Information Systems) but which does much of what you'd want in a CAD program. (No circles/curves, though, only points, lines, and polygons...) – adv12 Dec 28 '15 at 21:51

2 Answers2

1

This is going to be a serious development effort, so you should definitely check if any open source or 3rd party libraries can meet your needs before reinventing everything. However, if you do proceed to roll your own solution from the ground up, I would recommend using a LineSegment class as your fundamental (atomic object), instead of a List<PointF>. The essential member fields in this proposed LineSegment class would be px, py, qx, and qy, which represent the coordinates of the two endpoints of the line segment.

The "moveable" line segment graphics solution you posted above would work naturally with this object. Also intersection tests between any two LineSegment objects could be accomplished with the logic outlined here: http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/

If you wanted to create a connected series of line segments, you could place these individual LineSegment objects into a List<LineSegment> (or alternatively add a public LineSegment Next; member reference into the class to connect any two objects together in a linked-list fashion). I realize there would be some redundancy because the 2nd point of each segment would be identical to the 1st point of the next segment (if the segments are indeed spatially connected), but I can say from experience that this atomic structure will be much easier to work with in the long run than simple points, especially when splicing lines, snipping subsections, passing them to helper functions, etc.

The LineSegment class can also be naturally extended to support further line-specific properties like labels, line colors, line widths, etc. that couldn't naturally be assigned to just a list of points. Even curved lines in CAD programs are generally extensions of a straight lines (see how Bézier curves can be generated from line segments).

Hope this helps.

Special Sauce
  • 5,338
  • 2
  • 27
  • 31
  • Thanks Special Sauce. I appreciate the recommendations and that line segment web page is exactly what I need to do for the next stage of the process. I still might go the OpenGL route, but it seems to be quite an investment of time. – SojourningStudent Dec 29 '15 at 19:03
0

I posted the working code below. Basically a curve is made up of line segments which are made up of two GraphLines. It checks to see if a curve has one of its line segments selected, and then lights up the whole curve if true. See LineMover_Paint for that code.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LightUpWholeCurve
{

public partial class Form1 : Form
{
    public List<GraphLine> SinArr = new List<GraphLine>();
    public List<GraphLine> Bowditch = new List<GraphLine>();
    public List<GraphLine> CircArr = new List<GraphLine>();


    public List<List<GraphLine>> MasterArr = new List<List<GraphLine>>();


    public List<GraphLine> MasterArrayOfGraphLines = new List<GraphLine>();




    GraphLine SelectedLine = null;
    MoveInfo Moving = null;



    public Form1()
    {
        this.DoubleBuffered = true;

        this.Paint += new PaintEventHandler(LineMover_Paint);
        this.MouseMove += new MouseEventHandler(LineMover_MouseMove);
        this.MouseDown += new MouseEventHandler(LineMover_MouseDown);
        this.MouseUp += new MouseEventHandler(LineMover_MouseUp);

        MakeSinArray();
        MakeBowditchArray();
        MakeCircleArray();


        //Create a lists of lists of each curve
        this.MasterArr.Add(SinArr);
        this.MasterArr.Add(Bowditch);
        this.MasterArr.Add(CircArr);

        foreach (var fullcurve in MasterArr)
        {
            foreach (var GL in fullcurve)
            {
                MasterArrayOfGraphLines.Add(GL);
            }
        }




        //You must use initialize component or you'll get a small window
        //Also, keep in mind that if you cut and paste a whole file you
        //must change the namespace, or it won't recognize "InitializeComponent
        InitializeComponent();
    }

    void MakeBowditchArray()
    {
        int numberOfSeg = 100;
        double TwoPI = (float)(2 * Math.PI) / numberOfSeg;
        for (int t = 0; t <= numberOfSeg; t++)
            this.Bowditch.Add(new GraphLine(
                500 + 25 * (float)Math.Sin(3 * TwoPI * t),
                300 + 25 * (float)Math.Cos(5 * TwoPI * t),
                500 + 25 * (float)Math.Sin(3 * TwoPI * (t + 1)),
                300 + 25 * (float)Math.Cos(5 * TwoPI * (t + 1))));

    }

    void MakeSinArray()
    {
        int numberOfSeg = 100;
        double TwoPI = (float)(2 * Math.PI) / numberOfSeg;
        for (int t = 0; t <= numberOfSeg; t++)
            this.SinArr.Add(new GraphLine(
                200 + 25 * (float)t / 20,
                200 + 25 * (float)Math.Sin(3 * TwoPI * t),
                200 + 25 * (float)(t + 1) / 20,
                200 + 25 * (float)Math.Sin(3 * TwoPI * (t + 1))));
    }

    void MakeCircleArray()
    {
        int numberOfSeg = 50;
        double TwoPI = (float)(2 * Math.PI) / numberOfSeg;
        for (int t = 0; t <= numberOfSeg; t++)
            this.CircArr.Add(new GraphLine(
                300 + 25 * (float)Math.Sin(TwoPI * t),
                300 + 25 * (float)Math.Cos(TwoPI * t),
                300 + 25 * (float)Math.Sin(TwoPI * (t + 1)),
                300 + 25 * (float)Math.Cos(TwoPI * (t + 1))));
    }



    private void LineMover_MouseUp(object sender, MouseEventArgs e)
    {
        if (Moving != null)
        {
            this.Capture = false;  //Capture is part of Control.Capture
            Moving = null;
        }
        RefreshLineSelection(e.Location);
    }

    private void LineMover_MouseDown(object sender, MouseEventArgs e)
    {
        RefreshLineSelection(e.Location);
        if (this.SelectedLine != null && Moving == null)
        {
            this.Capture = true; //gets or sets a bool whether control has captured the mouse.
            Moving = new MoveInfo
            {
                Line = this.SelectedLine,
                StartLinePoint = SelectedLine.StartPoint,
                EndLinePoint = SelectedLine.EndPoint,
                StartMoveMousePoint = e.Location
            };
        }
        RefreshLineSelection(e.Location);

    }

    private void LineMover_MouseMove(object sender, MouseEventArgs e)
    {
        if (Moving != null)
        {

            Moving.Line.StartPoint = new PointF(Moving.StartLinePoint.X + e.X - Moving.StartMoveMousePoint.X, Moving.StartLinePoint.Y + e.Y - Moving.StartMoveMousePoint.Y);
            Moving.Line.EndPoint = new PointF(Moving.EndLinePoint.X + e.X - Moving.StartMoveMousePoint.X, Moving.EndLinePoint.Y + e.Y - Moving.StartMoveMousePoint.Y);
        }
        RefreshLineSelection(e.Location);
    }




    private void LineMover_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
        e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;


        //Look at every GraphLine in SinArray and if it is the segment selected, 
        //then turn it to the color Red
        Color color1 = Color.Blue;
        Pen pen1 = new Pen(color1, 2);

        foreach (var line in SinArr)
        {
            if (line == SelectedLine)
            {
                color1 = Color.Red;
                pen1 = new Pen(color1, 2);
                break;
            }
        }

        foreach (var line in SinArr)
        {
            e.Graphics.DrawLine(pen1, line.StartPoint, line.EndPoint);
        }

        //Go through entire array in Bowditch and check to see if any line was selected.
        //If it was, then set color to Red

        color1 = Color.Blue;
        pen1 = new Pen(color1, 2);

        foreach (var line in Bowditch)
        {
            if (line == SelectedLine)
            {
                color1 = Color.Red;
                pen1 = new Pen(color1, 2);
                break;
            }   
        }

        foreach (var line in Bowditch)
        {
            e.Graphics.DrawLine(pen1, line.StartPoint, line.EndPoint);
        }






        color1 = Color.Blue;
        pen1 = new Pen(color1, 2);

        foreach (var line in CircArr)
        {
            if (line == SelectedLine)
            {
                color1 = Color.Red;
                pen1 = new Pen(color1, 2);
                break;
            }
        }

        foreach (var line in CircArr)
        {
            e.Graphics.DrawLine(pen1, line.StartPoint, line.EndPoint);
        }


    }


    private void RefreshLineSelection(Point point)
    {



        var selectedLine = FindLineByPoint(MasterArrayOfGraphLines, point);
        if (selectedLine != this.SelectedLine)
        {
            this.SelectedLine = selectedLine;
            this.Invalidate();
        }
        if (Moving != null)
            this.Invalidate();



        this.Cursor =
            Moving != null ? Cursors.Hand :
            SelectedLine != null ? Cursors.SizeAll :
              Cursors.Default;

    }


    static GraphLine FindLineByPoint(List<GraphLine> lines, Point p)
    {
        var size = 40;
        var buffer = new Bitmap(size * 2, size * 2);

        foreach (var line in lines)
        {
            //draw each line on small region around current point p and check pixel in point p 
            //does it really draw all the lines from this.Lines = new List<GraphLine>() ? [I wrote that]

            using (var g = Graphics.FromImage(buffer))
            {
                g.Clear(Color.Black);  //Makes entire buffer black
                g.DrawLine(new Pen(Color.Green, 3),  //makes a line through it green
                    line.StartPoint.X - p.X + size,
                    line.StartPoint.Y - p.Y + size,
                    line.EndPoint.X - p.X + size,
                    line.EndPoint.Y - p.Y + size);
            }

            if (buffer.GetPixel(size, size).ToArgb() != Color.Black.ToArgb())
                return line;
        }
        return null;
    }


    public class MoveInfo
    {
        public GraphLine Line;
        public PointF StartLinePoint;
        public PointF EndLinePoint;
        public Point StartMoveMousePoint;
    }
    public class GraphLine
    {
        public GraphLine(float x1, float y1, float x2, float y2)
        {
            this.StartPoint = new PointF(x1, y1);
            this.EndPoint = new PointF(x2, y2);
        }
        public PointF StartPoint;
        public PointF EndPoint;
    }

    private void Form1_Load(object sender, EventArgs e)
    {

    }

}
}