views:

171

answers:

1

Hi, I'm involved in a project in which we're doing a visual editor (written in Java). Now, I'm trying to make curves that join two different objects that I'm painting in a class that extends JPanel (this class is what I'm using to paint, inside a JFrame, overriding the method paintComponent). I'm in troubles because I'm using the class QuadCurve2D to make this, but I cannot make it clickable (I'm using the method contains, but it doesn't work everytime), make it editable (for example, setting a square in its middle point to modify its curvature. The point that is used on the middle of the QuadCurve2D when the constructor is called is outside the curve) or something (method, variable, iterator, etc) that could tell me which Points are in the QuadCurve2D.

After looking for all of that some time, I have no answer, so I'm trying posting it here to find a solution. Is there anyway to make it with the QuadCurve2D class, or do I have to try with some external library?

+3  A: 

First of all sorry for the long reply. I am now posting a complete answer to your question. I am sub classing the QuadCurve2D.Double class and with a little math now you define the curve with a start,end and a middle point instead of a control point. Also i have created a new method that checks whether a point is on the curve. The intersects method checks if the convex hull of the shape intersects with the provided shape so in the case of the concave curve this is functional but not accurate. Note that my implementation of the method to check whether a point is on the curve is rather computationally expensive and not 100% accurate since i am checking along the curve length with a specified resolution (0 is the beginning of the curve, 1 is the end. So in the example provided i am checking with a resolution of 0.01 meaning 100 checks are made along the curve). For that matter make sure that the provided step in the resolution is a divider of 0.5 (the middle point) so that you may be able to select it. If that makes no sense don't pay attention it doesn't really matter, you can use my example out of the box. Note that i also provide a check box to switch between the intersects method and my own for checking whether the mouse is on the curve. And when using my new method, i also provide a slider to specify the resolution so that you may see the effects of various values. Here are the classes.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;

import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;

@SuppressWarnings("serial")
public class CurvePanel extends JPanel implements MouseListener,MouseMotionListener{

    Point2D startPoint = new Point2D.Double(50, 50);
    Point2D middlePoint = new Point2D.Double(100,80);
    Point2D endPoint = new Point2D.Double(200, 200);
    Point2D[] points = new Point2D[] {startPoint,middlePoint,endPoint};
    QuadCurveWithMiddlePoint curve;
    private Point2D movingPoint;
    private boolean dragIt = false;
    private boolean showControls = false;
    JCheckBox useNewMethod;
    JSlider resolution;

    public CurvePanel() {
     setPreferredSize(new Dimension(300,300));
     addMouseListener(this);
     addMouseMotionListener(this);
     curve = new QuadCurveWithMiddlePoint();
     useNewMethod = new JCheckBox("Use new \"contains\" method");
     resolution = new JSlider(JSlider.HORIZONTAL,1,10,1);
     useNewMethod.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
       resolution.setEnabled(useNewMethod.isSelected());
      }
     });
     useNewMethod.setSelected(false);
     resolution.setEnabled(false);
     setCurve();
    }

    private void setCurve() {
     curve.setCurveWithMiddlePoint(startPoint, middlePoint, endPoint);
    }

    public static void main(String[] args) {
     JFrame f = new JFrame("Test");
     CurvePanel panel = new CurvePanel();
     f.getContentPane().setLayout(new BorderLayout());
     f.getContentPane().add(panel.useNewMethod,BorderLayout.NORTH);
     f.getContentPane().add(panel,BorderLayout.CENTER);
     f.getContentPane().add(panel.resolution,BorderLayout.SOUTH);
     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     f.pack();
     f.setVisible(true);
    }

    @Override
    public void mouseClicked(MouseEvent e) {}
    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
    @Override
    public void mousePressed(MouseEvent e) {
     for (Point2D point : points) {
      if (e.getPoint().distance(point) <= 2) {
       movingPoint = point;
       dragIt = true;
      }
     }
    }
    @Override
    public void mouseReleased(MouseEvent e) {
     dragIt = false;
    }
    @Override
    public void mouseDragged(MouseEvent e) {
     if (dragIt) {
      movingPoint.setLocation(e.getPoint());
      setCurve();
      repaint();
     }
    }
    @Override
    public void mouseMoved(MouseEvent e) {
     if (useNewMethod.isSelected())
      showControls = curve.pointOnCurve(e.getPoint(), 2, resolution.getValue()/100.0);
     else
      showControls = curve.intersects(e.getX()-2, e.getY()-2, 4, 4);
     repaint();
    }
    @Override
    public void paintComponent(Graphics g) {
     Graphics2D g2 = (Graphics2D)g;
     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
     g2.setPaint(Color.white);
     g2.fillRect(0, 0, getWidth(), getHeight());
     g2.setPaint(Color.black);
     g2.draw(curve);
     if (showControls)
      for (Point2D point : points) {
       g2.setPaint(Color.black);
       g2.drawOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
       g2.setPaint(Color.red);
       g2.fillOval((int)point.getX()-2, (int)point.getY()-2, 4, 4);
      }
    }
}

And also :

import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D.Double;

@SuppressWarnings("serial")
public class QuadCurveWithMiddlePoint extends Double {

    private Point2D middlePoint = new Point2D.Double();
    private final double L = 0.5;

    public QuadCurveWithMiddlePoint(double x1,double y1, double xm, double ym, double x2, double y2) {
     super(x1,y1,xm,ym,x2,y2);
     setMiddlePoint(xm, ym);
    }

    public QuadCurveWithMiddlePoint() {
     this(0,0,0,0,0,0);
    }

    public Point2D getMiddlePoint() {
     calculateMiddlePoint();
     return middlePoint;
    }

    public void setMiddlePoint(double middleX, double middleY) {
     setCurve(getP1(), getControlPointByMiddle(middleX, middleY), getP2());
     calculateMiddlePoint();
    }

    public void setMiddlePoint(Point2D middle) {
     setMiddlePoint(middle.getX(),middle.getY());
    }

    private Point2D getControlPointByMiddle(double middleX,double middleY) {
     double cpx = (middleX-(L*L-2*L+1)*x1-(L*L)*x2)/(-2*L*L+2*L);
     double cpy = (middleY-(L*L-2*L+1)*y1-(L*L)*y2)/(-2*L*L+2*L);
     return new Point2D.Double(cpx,cpy);
    }

    private Point2D calculatePoint(double position) {
     if (position<0 || position>1)
      return null;
     double middlex = (position*position-2*position+1)*x1+(-2*position*position+2*position)*ctrlx+(position*position)*x2;
     double middley = (position*position-2*position+1)*y1+(-2*position*position+2*position)*ctrly+(position*position)*y2;
     return new Point2D.Double(middlex,middley);
    }

    public void calculateMiddlePoint() {
     middlePoint.setLocation(calculatePoint(L));
    }

    public void setCurveWithMiddlePoint(double xx1,double yy1, double xxm, double yym, double xx2, double yy2) {
     setCurve(xx1, yy1, xxm, yym, xx2, yy2);
     setMiddlePoint(xxm,yym);
    }

    public void setCurveWithMiddlePoint(Point2D start, Point2D middle, Point2D end) {
     setCurveWithMiddlePoint(start.getX(),start.getY(),middle.getX(),middle.getY(),end.getX(),end.getY());
    }

    public boolean pointOnCurve(Point2D point, double accuracy, double step) {
     if (accuracy<=0)
      return false;
     if (step<=0 || step >1)
      return false;
     boolean oncurve = false;
     double current = 0;
     while (!oncurve && current <= 1) {
      if (calculatePoint(current).distance(point)<accuracy)
       oncurve = true;
      current += step;
     }
     return oncurve;
    }

}

If you want to know how i made the class, do a search for basic linear algebra and also search Wikipedia for Bézier curves.

Savvas Dalkitsis
Thank you for your answer. That's exactly what I have done, but what I need is the same but setting the Ellipse2D just in the middle point inside the curve. How can I get that point?
Please see the full answer i posted. It will provide you with a working class with a control point in the middle of the curve.
Savvas Dalkitsis