1

I want a line to be drawn till the edge of the screen when I provide to it 2 points and an angle (for the direction). For example if the first mouse point is 4,4 and the next mouse point is 6,6 so from those points you know that the line has a North-East direction, then a line should be drawn from 4,4 till the end of the screen and passing through 6,6. Note: after the mouse gets to 6,6 the line should be drawn till the edge of the screen while the mouse is still at 6,6.

enter image description here

Also this should be done in MouseMoved with no clicks preferably, this means that both mouse points are obtained from MouseMoved. I tried for a whole day to get an output but no use.

Sherif
  • 47
  • 2
  • 8
  • can you post some code? have you tried any thing? where are you stuck? – elyashiv Feb 26 '16 at 10:17
  • why do you need two points and a angle? you need a point and an angle, or two points to draw a line... – elyashiv Feb 26 '16 at 10:18
  • So basically what you have is a "what is the point on a circle for a given angle" problem. Start by understanding how to solve that problem. When you know how to do that, create a virtual circle whose radius is `Math.max(width/2, height/2)`, calculate the point on the circle for a given angel and draw a line to it – MadProgrammer Feb 26 '16 at 10:20
  • @elyashiv added some code – Sherif Feb 26 '16 at 10:23

1 Answers1

8

You need to break you problem down...

First, you need to be able to calculate the angle between two points

double angle = Math.atan2(toY - fromY, toX - fromX);

wow, that was kind of easy (thank you Internet)

Next, we need to be able to calculate a point on the radius of circle (okay, this might sound weird, but it's the easiest solution I could think off...and I know I can solve it, thank you Internet)

toX = (int) (Math.round(fromX + (radius * Math.cos(angle))));
toY = (int) (Math.round(fromY + (radius * Math.sin(angle))));

What we're going to do is, create a circle so large that it expands beyond the visible frame boundaries and draw our line out to! Easy!

Follow the dots

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Point from;
        private Point clickTo;
        private Point to;

        public TestPane() {
            addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    if (to != null) {
                        to = null;
                        clickTo = null;
                        from = null;
                    }
                    if (from != null) {
                        to = e.getPoint();
                        clickTo = new Point(to);
                        double angle = Math.atan2(to.y - from.y, to.x - from.x);
                        int radius = Math.max(getWidth(), getHeight()) * 2;

                        to.x = (int) (Math.round(from.x + (radius * Math.cos(angle))));
                        to.y = (int) (Math.round(from.y + (radius * Math.sin(angle))));
                    } else {
                        from = e.getPoint();
                    }
                    repaint();
                }
            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (from != null) {
                g2d.setColor(Color.RED);
                g2d.fillOval(from.x - 4, from.y - 4, 8, 8);

                if (to != null) {
                    g2d.setColor(Color.GREEN);
                    g2d.fillOval(clickTo.x - 4, clickTo.y - 4, 8, 8);

                    g2d.setColor(Color.BLUE);
                    g2d.drawLine(from.x, from.y, to.x, to.y);
                }
            }
            g2d.dispose();
        }

    }

}

With center anchor point....

and MouseMotionListener support

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Point from;
        private Point clickTo;
        private Point to;

        public TestPane() {
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    from = new Point(getWidth() / 2, getHeight() / 2);
                    to = e.getPoint();
                    clickTo = new Point(to);
                    double angle = Math.atan2(to.y - from.y, to.x - from.x);
                    int radius = Math.max(getWidth(), getHeight()) * 2;

                    to.x = (int) (Math.round(from.x + (radius * Math.cos(angle))));
                    to.y = (int) (Math.round(from.y + (radius * Math.sin(angle))));
                    repaint();
                }

            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (from != null) {
                g2d.setColor(Color.RED);
                g2d.fillOval(from.x - 4, from.y - 4, 8, 8);

                if (to != null) {
                    g2d.setColor(Color.GREEN);
                    g2d.fillOval(clickTo.x - 4, clickTo.y - 4, 8, 8);

                    g2d.setColor(Color.BLUE);
                    g2d.drawLine(from.x, from.y, to.x, to.y);
                }
            }
            g2d.dispose();
        }

    }

}

One more small thing, i need the line to have a flexible length, by that i mean it should be within the screen, and since this is a rectangle then the width and height will differ, and having a fixed length will make problems because it will be long at some parts and short at others, any idea ?

So, you need to know where the line collides with the boundaries of the rectangle, which basically boils down to line collision detection (because a rectangle is just four lines)

So, Internet to the rescue

I took the idea slightly further and made a method which took a Rectangle and a Line2D and returned either a Point2D where the collision point occurs or null if no collision occurred (in this case, we should be 99.9% guaranteed of a collision)

Shortended

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Point from;
        private Point clickTo;
        private Point to;

        public TestPane() {
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    from = new Point(getWidth() / 2, getHeight() / 2);
                    to = e.getPoint();
                    clickTo = new Point(to);
                    double angle = Math.atan2(to.y - from.y, to.x - from.x);
                    int radius = Math.max(getWidth(), getHeight()) * 2;

                    to.x = (int) (Math.round(from.x + (radius * Math.cos(angle))));
                    to.y = (int) (Math.round(from.y + (radius * Math.sin(angle))));
                    repaint();
                }

            });
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        public Point2D getIntersectionPoint(Line2D line1, Line2D line2) {
            if (!line1.intersectsLine(line2)) {
                return null;
            }
            double px = line1.getX1(),
                    py = line1.getY1(),
                    rx = line1.getX2() - px,
                    ry = line1.getY2() - py;
            double qx = line2.getX1(),
                    qy = line2.getY1(),
                    sx = line2.getX2() - qx,
                    sy = line2.getY2() - qy;

            double det = sx * ry - sy * rx;
            if (det == 0) {
                return null;
            } else {
                double z = (sx * (qy - py) + sy * (px - qx)) / det;
                if (z == 0 || z == 1) {
                    return null;  // intersection at end point!
                }
                return new Point2D.Float(
                        (float) (px + z * rx), (float) (py + z * ry));
            }
        } // end intersection line-line

        public Point2D getIntersectionPoint(Line2D line, Rectangle bounds) {
            Point2D top = getIntersectionPoint(line, new Line2D.Double(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y));
            Point2D bottom = getIntersectionPoint(line, new Line2D.Double(bounds.x, bounds.y + bounds.height, bounds.x + bounds.width, bounds.y + bounds.height));
            Point2D left = getIntersectionPoint(line, new Line2D.Double(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height));
            Point2D right = getIntersectionPoint(line, new Line2D.Double(bounds.x + bounds.width, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height));

            return top != null ? top 
                    : bottom != null ? bottom 
                    : left != null ? left
                    : right != null ? right
                    : null;
        } 

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();
            Rectangle bounds = new Rectangle(50, 50, getWidth() - 100, getHeight() - 100);
            g2d.draw(bounds);

            if (from != null) {
                g2d.setColor(Color.RED);
                g2d.fillOval(from.x - 4, from.y - 4, 8, 8);

                if (to != null) {
                    g2d.setColor(Color.GREEN);
                    g2d.fillOval(clickTo.x - 4, clickTo.y - 4, 8, 8);

                    Line2D line = new Line2D.Double(from, to);
                    g2d.setColor(Color.BLUE);
                    g2d.draw(line);

                    Point2D intersectPoint = getIntersectionPoint(line, bounds);

                    g2d.setColor(Color.MAGENTA);
                    g2d.fill(new Ellipse2D.Double(intersectPoint.getX() - 4, intersectPoint.getY() - 4, 8, 8));

                    g2d.draw(new Line2D.Double(from, intersectPoint));
                }
            }
            g2d.dispose();
        }

    }

}

So, know you have a projection of the line beyond the visible boundaries of the component (which would helpful in your other question) and know where the line interests the inner boundaries

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thanks alot, and Im sorry but when math meets java I get lost. – Sherif Feb 26 '16 at 11:28
  • One small question. used it in my code and it worked but the problem is that the angle in jumping so the line is not accurate, also i get my mouse points from mouse moved – Sherif Feb 26 '16 at 11:52
  • how can i make the angle contain more digits, by that i mean not jumping, Not 90, 45, 270. But instead it can be like this 91,92,93...etc – Sherif Feb 26 '16 at 14:25
  • also is it possible to do it but with a rectangle instead of a circle ? – Sherif Feb 26 '16 at 17:25
  • Use a rectangle instead of circle, I'm going to say probably not, because I'd be basically the same problem, except now you're adding in some kind of collision detection. The angles are in radians and should pretty accurate – MadProgrammer Feb 26 '16 at 20:25
  • this occurs when I obtain both the new and old mouse points from mousemoved, then when i move the mouse the angles become way less accurate and become fixed with a certain values. – Sherif Feb 26 '16 at 20:50
  • I did a quick test using a `MouseMotionListener` and had no issues, it was smooth as – MadProgrammer Feb 26 '16 at 20:55
  • You got the Old mouse points from MouseMoved and the New mouse points from MouseMoved and is smooth and pointing correctly ? – Sherif Feb 26 '16 at 20:59
  • No, I used the click point as an anchor and moved around it. So you're trying to determine the difference between each move? – MadProgrammer Feb 26 '16 at 21:07
  • I tried making the Old mouse a click point and the New mouse point a motion one and its smooth, but whats required from me is to get both points from MouseMoved, no clicking involved. I tried that and it didnt work. – Sherif Feb 26 '16 at 21:20
  • The problem is, the angles between the two points are to small. Maybe you need to consider having a "anchor" point from which you measure the angle from (like the center of the screen) – MadProgrammer Feb 26 '16 at 21:49
  • I tried having mid screen as anchor but the angles are totally wrong, I am so sorry to bother you but i am really bad at math. – Sherif Feb 26 '16 at 22:12
  • It's really very simple, the anchor point becomes a fixed point in the middle of the component, but you might want to update it with the `MouseMotionListener`, for example `from = new Point(getWidth() / 2, getHeight() / 2);`, also see update – MadProgrammer Feb 26 '16 at 22:22
  • Thanks alot, One more small thing, i need the line to have a flexible length, by that i mean it should be within the screen, and since this is a rectangle then the width and height will differ, and having a fixed length will make problems because it will be long at some parts and short at others, any idea ? – Sherif Feb 27 '16 at 00:33
  • You could do collision detection, calculating the point at which the line intersects your virtual bounds, you could use a clipping rectangle or a separate component – MadProgrammer Feb 27 '16 at 00:34
  • so there is no way to change the length of the line depending where im facing unless i collide with something or intersect ? – Sherif Feb 27 '16 at 00:50
  • Put it this way, you know the start point, you can calculate the point the line cross the rectangle (which equates down to line intersection), so you just draw a line between those two points – MadProgrammer Feb 27 '16 at 00:52
  • Thanks alot, you really saved me, I'm really thankful for your help and quick response, Have a nice day and sorry if I caused any inconvenience, Thanks again :) – Sherif Feb 27 '16 at 13:20