Search code examples
javadrawinggraphics2dmouselistenerpath-2d

Keep the same distance between 2 Path2D onMouseDragged


I have let's say two Path2D, one contains the other. When I move a point from first shape, the second shape move the same, but because of the angle changing, the distance between shapes changes too (and the final result...).

I have this so far, with hard coded points for the second triangle(innerTriangle):

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;

class DragTest extends JPanel {

    private final class MouseDrag extends MouseAdapter {
        private boolean dragging = false;
        private Point last;


        @Override
        public void mousePressed(MouseEvent m) {     
            if(dRect.contains(m.getPoint())){
                last = m.getPoint();
                dragging = isInsideRect(dRect, last);
                if (!dragging) {
                    x = last.x;
                    y = last.y;
                    width = 0;
                    height = 0;
                }
            }
            repaint();
        }

        @Override
        public void mouseReleased(MouseEvent m) {
            last = null;
            dragging = false;
            repaint();
        }

        @Override
        public void mouseDragged(MouseEvent m) {
            if(dRect.contains(m.getPoint())){
                int dx = m.getX() - last.x;
                int dy = m.getY() - last.y;
                if (dragging) {
                    x += dx;
                    y += dy;
                } else {
                    width += dx;
                    height += dy;
                }
                last = m.getPoint();
            }
            repaint();
        }
    }

    private int x;
    private int y;
    private int width;
    private int height;
    private Rectangle2D.Float dRect ;
    private Path2D triangle = new Path2D.Float();
    private Path2D innerTriangle = new Path2D.Float();

    private Point p1;
    private Point p2 = new Point(5,200);
    private Point p3 = new Point(400,200);

    private MouseDrag mouseDrag;

    public DragTest() {
        setBackground(Color.WHITE);
        dRect = new Rectangle2D.Float(x, y, 10+width, 10+height);
        mouseDrag = new MouseDrag();
        addMouseListener(mouseDrag);
        addMouseMotionListener(mouseDrag);


    }

    public boolean isInsideRect(Shape s, Point point) {  
            return s.contains(point);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;

        dRect = new Rectangle2D.Float(x, y , 10, 10);
        g2.draw(dRect);

        triangle.reset();
        triangle.moveTo(dRect.getCenterX(), dRect.getCenterY());
        p1 = new Point((int)dRect.getCenterX(), (int)dRect.getCenterY());
        triangle.lineTo(p2.x, p2.y);
        triangle.lineTo(p3.x, p3.y);
        triangle.closePath();

       innerTriangle.reset();
       innerTriangle.moveTo(p1.x+10, p1.y+17);
       innerTriangle.lineTo(p2.x+10, p2.y-10);
       innerTriangle.lineTo(p3.x-47, p3.y-10);
       innerTriangle.closePath();



        g2.draw(triangle);
        g2.draw(innerTriangle);

        g2.dispose();

    }

    public static void main(String[] args) {
        JFrame jFrame = new JFrame();
        jFrame.setSize(800, 600);
        jFrame.add(new DragTest());
        jFrame.setVisible(true);
        jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

How can I maintain the same distance all around (say 10px), programmatically?

The distance between the outer triangle and the red (inner) triangle should be the same always, in all directions. Any idea?

SEE IMAGE

Currently I'm working on something:

// Calculate distance from point to line    
public double pointToLineDistance(Point A, Point B, Point P) {
    double normalLength = Math.sqrt((B.x-A.x)*(B.x-A.x)+(B.y-A.y)*(B.y-A.y));
    return Math.abs((P.x-A.x)*(B.y-A.y)-(P.y-A.y)*(B.x-A.x))/normalLength;
}

// initially set :
// pi1.x = p1.x;
// pi1.y = p1.y;
// -------------------- Then ------------------------------

 while(pointToLineDistance(p1, p3, pi1) == 10 && pointToLineDistance(p1, p2, pi1) == 10){
        pi1.y++;
        pi1.x++;            
        pi1.setLocation(pi1.x, pi1.y);
    }

... but doesn't work. HELP!!! :)

SEE IMAGE FOR THE CODE ABOVE

Thank You!


Solution

  • Keeping even spacing at line joins.

    This problem needs a illistration to help explain. The solution is for one corner but the process will work for as many corners as you want.

    Given two lines joined at one end find the two line offset by a fixed amount from both lines

    enter image description here

    Bold letters (example A) represent points. Lines as bold joined letters from start to end (example AB is line from A to B and BA is the line from B to A)

    Given the lines AB and BC find the lines A1D and DC1 so that the area x distance from the original lines.

    // all variables are assumed declared and floats or doubles (up to you)
     Ax = 10;   // x of Point A
     Ay = 200;  // y of A
     Bx = 200;  // x,y of B
     By = 200; 
     Cx = 40;   // C
     Cy = 10;
    
     Dist = 40; // distance from the line
    

    Looking at the image we can see some nice symmetry that can be exploited to find the solution. The lines EB, BF, FD and DE are all the same length. That means that if we solve the right triangle gBF we can move along the line BA from B to g and from g to h as gh is the same length as FB

    From this point the line AB is turned around to BA

    Finding a point x distance from a line

    To find the point A1 we find the normalised vector along the line BA

    Finding A1

    // get the vector from B to A and normalise it 
    BAx = Ax - Bx;
    BAy = Ay - By;
    leng = Math.sqrt(BAx * BAx + BAy * BAy);
    BAx /= leng; // The vector BA is 1 pixel (unit long)
    BAy /= leng;
    

    The normal moves along the line to go at 90 deg just swap x and y negating y

    A1x = Ax - BAy * Dist;
    A1y = Ay + BAx * Dist;
    

    Now do the same for the point C1

    BCx = Cx - Bx;
    BCy = Cy - By;
    leng = Math.sqrt(BCx * BCx + BCy * BCy);
    BCx /= leng; 
    BCy /= leng;
    C1x = Cx + BCy * Dist;  // move in the opposite direction than from A
    C1y = Cy - BCx * Dist;
    

    Finding D by solving the triangle FBg

    If you only needed to find D (eg you are doing this on a closed line, triangle) you still have to get the normalised vectors BA and BC that were worked out above. BAx, BAy and BCx, BCy

    To find the sin of the angle EBF use get the cross product of the two normalised vectors BA and BC. The sin of an angle relates the opposite side of a triangle Fg to the Hypotenuse BF as sin(ang) = opp/hypt

    cross = BAx * BCy - BAy * BCx; // gets the sin of the angle
    // you should check cross of zero. If so there is no solution
    FBlen = Dist / cross; // gets the hypot the length of FB
    

    Now we have FB we know the distance gh we could get gB as we have two sides of a right triangle and the length of gB = sqrt(FB*FB+Dist*Dist) but this will become an issue if the angle of ABC is greater than 90 deg. Not only do we need to know the length but the direction (positive or negative).

    We can solve for Bg using the dot product which finds the cosine of the angle ABC which relates the hypotenuse to the adjacent side of a right triangle cos(ang) = BF/Bg

    dot = BAx * BCx + BAy * BCy;
    Bglen = FBlen * dot;
    

    Now we can move along the line BA to g then to h then turn 90deg and out to D

    // move from B to h using the normal vec of the line BA time the sum 
    // of the length FB
    hx = Bx + BAx * (FBlen + BgLen);
    hy = By + BAy * (FBlen + BgLen);
    Dx = hx - BAy * Dist;  // then at 90 deg dist along to D
    Dy = hy + BAx * Dist;
    

    And you are all done you have the points A1, D, C1

    Code in one go

    You may also prefer to use a vector lib to do the adding, normalising,cross product etc...

    Ax = 10;   // x of Point A
    Ay = 200;  // y of A
    Bx = 200;  // x,y of B
    By = 200; 
    Cx = 40;   // C
    Cy = 10;
    
    Dist = 40;  // dist out
    
    
    BAx = Ax - Bx;
    BAy = Ay - By;
    BCx = Cx - Bx;
    BCy = Cy - By;
    leng = Math.sqrt(BAx * BAx + BAy * BAy);
    BAx /= leng; 
    BAy /= leng;
    leng = Math.sqrt(BCx * BCx + BCy * BCy);
    BCx /= leng; 
    BCy /= leng;
    
    // get inside end points
    A1x = Ax - BAy * Dist;
    A1y = Ay + BAx * Dist;
    C1x = Cx + BCy * Dist;  
    C1y = Cy - BCx * Dist;
    
    FBlen = Dist / (BAx * BCy - BAy * BCx); 
    FBlen += FBlen * (BAx * BCx + BAy * BCy);
    Dx = Bx + BAx * FBlen - BAy * Dist;;
    Dy = By + BAy * FBlen + BAx * Dist;
    

    Edge cases

    You can do this for any number of lines as long as you go in the same direction and that your vectors are from the corners out. There are a few caveats.

    • If he distance between two corner points it too short the inside lines will overlap and cause problems. There is no simple solution to this apart from fixing the corner spacing and limiting the width.
    • Will fail if the lines are parallel. Check the result of the cross product, when zero or very close to zero line AB and BC are near parallel.
    • Miter extent. When the angle between the two lines starts to get near 360deg the point D will start getting further and further out. As the angle approaches 360deg or 2 Radians the distance from B to D will approch infinity. Again check cross product for zero or near zero. You can also set a miter limit by rounding the corner. or truncating it.

    enter image description here

    Mitering a corner. The above answer has given you the information you need to solve the above image. The distance DF is fixed to max miter limit, if the point B is further than F from D then find the point F. Look for symmetry and like triangles to find the other points.

    If you are making long lines you will be best of doing thein on each side of the line. By defining the center not the edge you get a better result.