I'm drawing a rectangle onto a canvas and a line from the center of the rectangle to some random point in the coordinate space.
Now, I want to truncate the line by the length that is inside the rectangle so that the line starts at the rectangle edge.
How could I do this?
Example
The rectangle could be defined by 2 points: Pstart(1, 3), Pend(3,
1)
The center point can be computed to : P(2, 2)
Now draw the line from P(2, 2) to Q(10, 2).
As I know the width of the rectangle is 2, I could tell the line to start at P(4, 2) instead of P(2, 2).
This gets more complicated when the point is not parallel to one of the XY axis. Moreover the length inside the rectangle will be different amount for diagonal lines.
How can I calculate the start offset for the line point with respect to the center of the rectangle and the end point of the line?
Probably I'd have to find the point where the line would cross the rectangle, and then just let the line start at the cross point. But how could I get this point?
Honestly, I don't understand the maths, but...
Essentially, you have 5 lines. The original line and the 4 lines of the rectangle. So if you break it down to a simple line intersection of line problem it should become a little easier...
import java.awt.BorderLayout;
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.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class IntersectPoint {
public static void main(String[] args) {
new IntersectPoint();
}
public IntersectPoint() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = (int) (getWidth() * 0.2f);
int y = (int) (getHeight() * 0.2f);
int width = (int) (getWidth() * 0.6f);
int height = (int) (getHeight() * 0.6f);
int x1 = x;
int y1 = 0;
int x2 = x + width;
int y2 = getHeight();
Line2D line = new Line2D.Double(x1, y1, x2, y2);
Rectangle2D rect = new Rectangle2D.Double(x, y, width, height);
Graphics2D g2d = (Graphics2D) g.create();
g2d.draw(rect);
g2d.draw(line);
g2d.setColor(Color.RED);
Point2D[] ps = getIntersectionPoint(line, rect);
for (Point2D p : ps) {
if (p != null) {
g2d.fill(new Ellipse2D.Double(p.getX() - 4, p.getY() - 4, 8, 8));
}
}
g2d.dispose();
}
public Point2D[] getIntersectionPoint(Line2D line, Rectangle2D rectangle) {
Point2D[] p = new Point2D[4];
// Top line
p[0] = getIntersectionPoint(line,
new Line2D.Double(
rectangle.getX(),
rectangle.getY(),
rectangle.getX() + rectangle.getWidth(),
rectangle.getY()));
// Bottom line
p[1] = getIntersectionPoint(line,
new Line2D.Double(
rectangle.getX(),
rectangle.getY() + rectangle.getHeight(),
rectangle.getX() + rectangle.getWidth(),
rectangle.getY() + rectangle.getHeight()));
// Left side...
p[2] = getIntersectionPoint(line,
new Line2D.Double(
rectangle.getX(),
rectangle.getY(),
rectangle.getX(),
rectangle.getY() + rectangle.getHeight()));
// Right side
p[3] = getIntersectionPoint(line,
new Line2D.Double(
rectangle.getX() + rectangle.getWidth(),
rectangle.getY(),
rectangle.getX() + rectangle.getWidth(),
rectangle.getY() + rectangle.getHeight()));
return p;
}
public Point2D getIntersectionPoint(Line2D lineA, Line2D lineB) {
double x1 = lineA.getX1();
double y1 = lineA.getY1();
double x2 = lineA.getX2();
double y2 = lineA.getY2();
double x3 = lineB.getX1();
double y3 = lineB.getY1();
double x4 = lineB.getX2();
double y4 = lineB.getY2();
Point2D p = null;
double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (d != 0) {
double xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
double yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
p = new Point2D.Double(xi, yi);
}
return p;
}
}
}
Look at Liang-Barsky algorithm used for line clipping by rectangle.
Vertices of the rectangle: a, b, c, d. Represent the x and y coord of each one like ax, ay, etc.
endpoints of the line: x, y
The line follows y = mx + b, and goes either up or down, right or left. That narrows down your possible rectangle edges for crossing to 2.
Use y = mx + b to determine the vertical coordinate at which it crosses the horizontal line, and the horizontal component at which it crosses the vertical line. Either only one of these will actually be on your rectangle (i.e., contained within one of the rectangle edges), or it will intersect at a corner.
Related
I have to make a program that generates stars in random locations of random size. My code already plots the stars in random locations, but I can't manage to randomly change their sizes. I tried assigning a size factor to each point to alter the distance between them but the stars came out all messed up. Is there a scaling method I can use?
Here is what I have so far, it plots the stars in random locations.
final int MID = WIDTH / 2;
final int TOP = 50;
//sky
Color skyColor = new Color(0, 0, 0);
page.fillRect(0,0,getWidth(),getHeight());
//ground
Color groundColor = new Color(95,95,95);
page.setColor(groundColor);
page.fillRect(0,HEIGHT-20,getWidth(),getHeight());
//star
for (int i = 1; i <= starCount; i++)
{
int ranLocX = gen.nextInt(700 - 100) + 100;
int ranLocY = gen.nextInt(300 - 75) + 75;
int ranSize = gen.nextInt(8 - 1) + 1;
int sizeXA = (-10 * ranSize);
int sizeXB = (10 * ranSize);
int sizeXC = (-5 * ranSize);
int sizeXD = (-10 * ranSize);
int sizeXE = (-10 * ranSize);
int sizeXF = (-10 * ranSize);
int sizeYC = (10 * ranSize);
int sizeYD = (-10 * ranSize);
int sizeYE = (10 * ranSize);
page.drawPolygon(new int[] {xa + ranLocX, xb + ranLocX, xc + ranLocX, xd + ranLocX, xe + ranLocX, xf + ranLocX}, new int[] {ya + ranLocY, yb + ranLocY, yc + ranLocY, yd + ranLocY, ye + ranLocY, yf + ranLocY}, 6);
}
Here is a simple method you can use to create a Shape with any given number of points and radius:
public static Shape radiusShape(int points, int... radii)
{
Polygon polygon = new Polygon();
for (int i = 0; i < points; i++)
{
double radians = Math.toRadians(i * 360 / points);
int radius = radii[i % radii.length];
double x = Math.cos(radians) * radius;
double y = Math.sin(radians) * radius;
polygon.addPoint((int)x, (int)y);
}
Rectangle bounds = polygon.getBounds();
polygon.translate(-bounds.x, -bounds.y);
return polygon;
}
To create your 5 point star you would use code like:
Shape star = ShapeUtils.radiusShape(10, 30, 12);
It will create a star with 5 outer points and 5 inner points to give the star shape.
So to randomize the size of the star you would randomize the radius.
Check out Playing With Shapes for more examples of the types of Shapes you can create using this method. The above radiusShape(...) method was taken from the ShapeUtils class found in the above link.
I would then suggest you create a custom class with the properties 1) Shape 2) Point so you can paint the Star at different locations on the panel. Then you create an ArrayList to hold instances of the class. In your painting method you iterate through this ArrayList to paint each Shape. The above link will also provide basic code for this concept.
Here's an example of how to change the size of a Polygon. I drew squares but any Shape will work.
just create a scale instance of an AffineTransform and use that to scale the Shape.
I used ThreadLocalRandom to randomly choose the scale to be applied. I always copy the original polygon(template) and then scale that.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Polygons extends JPanel {
static int WIDTH = 500;
static int HEIGHT = 500;
JFrame f = new JFrame();
Polygon b =new Polygon();
ThreadLocalRandom r = ThreadLocalRandom.current();
List<Shape> polys = new ArrayList<>();
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new Polygons().start());
}
public void start() {
f.add(this);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
Polygon template = new Polygon();
template.addPoint(0,0);
template.addPoint(0,100);
template.addPoint(100,100);
template.addPoint(100,0);
// AffineTransform rotate = AffineTransform.getRotateInstance(Math.toRadians(72.), )
for (int i = 0; i < 20; i++) {
Polygon p = new Polygon(template.xpoints,template.ypoints, template.npoints);
p.translate(r.nextInt(WIDTH), r.nextInt(HEIGHT));
double scale = r.nextInt(10,90)/100.;
AffineTransform scaleIt = AffineTransform.getScaleInstance(scale,scale);
polys.add(scaleIt.createTransformedShape(p));
}
}
public Dimension getPreferredSize() {
return new Dimension(WIDTH,HEIGHT);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Shape shape : polys) {
g2d.draw(shape);
}
}
}
Here is something I wrote a long time ago to create a 5 point star. It draws a single arm and then rotates 72 degrees and draws another, repeating the process.
Because it was written to allow the base to be changed, hence the star size, this might be as better option for scaling the size of your stars rather than using the AffineTransform mentioned above:
for (int i = 0; i < 50; i++) {
// get the base of the next star between 5 and 29 inclusive
int base = r.nextInt(5,30);
Polygon star = createStar(base);
// now randomly position it.
star.translate(r.nextInt(0,400),r.nextInt(0,400));
// and add to the list
polys.add(star);
}
Creating a 5 point star
int startx = 250; // arbitrary starting points
int starty = 250;
public Polygon createStar(int armBase) {
Polygon star = new Polygon();
// The armBase is equal to one side of the inner
// pentagon of the star
// The height of the arm is the distance from the middle of the
// base to the tip of the stars arm. Since the tangent computes
// ratio of the sides of a right triangle, multiplying by half
// the base gives the other side, hence the height.
int armHeight =
(int) (armBase / 2 * Math.tan(Math.toRadians(72)));
// The center offset is the distance from the middle of a given
// base to the center of the inner pentagon.
int centerOffset =
(int) (armBase / 2 * Math.tan(Math.toRadians(54)));
// this works by creating the first arm, rotating 72 degrees
// and then adding the other two coodinates of succeeding arms.
star.addPoint(startx, starty);
star.addPoint(startx + armBase / 2, starty - armHeight);
star.addPoint(startx + armBase, starty);
for (int j = 0; j < 4; j++) {
rotatePolygon(-Math.PI / 5 * 2, startx + armBase / 2,
starty + centerOffset, star);
star.addPoint(startx + armBase / 2, starty - armHeight);
star.addPoint(startx + armBase, starty);
}
star.npoints--;
star.translate(-star.getBounds().x,-star.getBounds().y);
return star;
}
// This is general purpose rotation that rotates about a center
// point. This can be derived using the double angle identities of
// for sin and cosine.
private void rotatePolygon(double ang, double sx, double sy,
Polygon poly) {
for (int j = 0; j < poly.npoints; j++) {
double x = poly.xpoints[j];
double y = poly.ypoints[j];
double xx = sx + (x - sx) * Math.cos(ang)
- (y - sy) * Math.sin(ang);
double yy = sy + (x - sx) * Math.sin(ang)
+ (y - sy) * Math.cos(ang);
poly.xpoints[j] = (int) xx;
poly.ypoints[j] = (int) yy;
}
}
Here's a GUI to draw one five-pointed star.
Here's the complete runnable code. I used polar coordinates to calculate the 10 points I needed to draw a star. I guessed the fraction to get the intermediate points correct.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Polygon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class StarryNight2GUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new StarryNight2GUI());
}
#Override
public void run() {
JFrame frame = new JFrame("Starry Night");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawingPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
public DrawingPanel() {
this.setBackground(Color.BLACK);
this.setPreferredSize(new Dimension(640, 480));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Color groundColor = new Color(95, 95, 95);
g.setColor(groundColor);
g.fillRect(0, getHeight() - 30, getWidth(), 30);
Polygon polygon = createStar(new Point(320, 240), 80);
g.setColor(Color.YELLOW);
g.fillPolygon(polygon);
}
private Polygon createStar(Point centerPoint, int radius) {
Polygon polygon = new Polygon();
// 72, 144, 216, 288, 360
// 36, 108, 180, 252, 324
// 54, 126, 198, 270, 342
// 18, 54, 90, 126, 162, 198, 234, 270, 306, 342
for (int angle = 18; angle < 360; angle += 72) {
double r = 0.42 * radius;
Point point = toCartesian(centerPoint, angle, r);
polygon.addPoint(point.x, point.y);
point = toCartesian(centerPoint, angle + 36, radius);
polygon.addPoint(point.x, point.y);
}
return polygon;
}
private Point toCartesian(Point centerPoint, int angle, double radius) {
double theta = Math.toRadians(angle);
int x = centerPoint.x + (int) Math.round(Math.cos(theta) * radius);
int y = centerPoint.y + (int) Math.round(Math.sin(theta) * radius);
return new Point(x, y);
}
}
}
How i want it to look like:
The circles move along with the arrow around the center circle.
How it is looking at the moment:
I want to draw a 2 lines between two circles. however these circles move all around the screen and i dont know a methodical way to draw lines between them. For example, I always have the top left corner of the two circles i want to draw a line between but thats it. I need help to draw a line in java that will adjust based on its position so that the lines move around the edge as the circles move
for (int z = 0; z < lines.size(); z++) {
if (lines.get(z).getfState().equals(states.get(a).getText()) && !lines.get(z).getfState().equals(lines.get(z).getnState())) {
transition.get(z).setIcon(null);
for (int x = 0; x < states.size(); x++) {
if (states.get(x).getText().equals(lines.get(z).getnState()) && states.get(a).getText().equals(lines.get(z).getfState())) {
int xbegin = (int) states.get(a).getBounds().getX();
int ybegin = (int) states.get(a).getBounds().getY();
int xend = (int) states.get(x).getBounds().getX();
int yend = (int) states.get(x).getBounds().getY();
if (xbegin > xend) {
Path2D.Double rect = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend + 30, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect.getBounds().x, rect.getBounds().y, rect.getBounds().width + 20, rect.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
} else {
if (xend - xbegin < 75) {
xbegin = xbegin - 20;
xend = xend - 20;
}
xbegin = xbegin + 5;
ybegin = ybegin + 25;
xend = xend + 5;
yend = yend + 25;
Path2D.Double rect = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect.getBounds().x, rect.getBounds().y, rect.getBounds().width + 20, rect.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
}
}
}
} else if (lines.get(z).getnState().equals(states.get(a).getText()) && !lines.get(z).getfState().equals(lines.get(z).getnState())) {
transition.get(z).setIcon(null);
for (int x = 0; x < states.size(); x++) {
if (states.get(x).getText().equals(lines.get(z).getfState()) && states.get(a).getText().equals(lines.get(z).getnState())) {
int xend = (int) states.get(a).getBounds().getX();
int yend = (int) states.get(a).getBounds().getY();
int xbegin = (int) states.get(x).getBounds().getX();
int ybegin = (int) states.get(x).getBounds().getY();
if (xbegin > xend) {
Path2D.Double rect2 = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend + 30, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect2.getBounds().x, rect2.getBounds().y, rect2.getBounds().width + 20, rect2.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
} else {
if (xend - xbegin < 75) {
xbegin = xbegin + 20;
xend = xend + 20;
}
xbegin = xbegin + 5;
ybegin = ybegin + 25;
xend = xend + 5;
yend = yend + 25;
Path2D.Double rect2 = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7));
OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7), Color.BLACK);
transition.get(z).setIcon(transit);
transition.get(z).setBounds(rect2.getBounds().x, rect2.getBounds().y, rect2.getBounds().width + 20, rect2.getBounds().height + 20);
jPanel2.revalidate();
jPanel2.repaint();
}
}
}
public static Path2D.Double createArrowForLine(
int fromPointx,
int fromPointy,
double rotationDeg,
double length,
double wingsAngleDeg) {
double ax = fromPointx;
double ay = fromPointy;
double radB = Math.toRadians(-rotationDeg + wingsAngleDeg);
double radC = Math.toRadians(-rotationDeg - wingsAngleDeg);
Path2D resultPath = new Path2D.Double();
resultPath.moveTo(length * Math.cos(radB) + ax, length * Math.sin(radB) + ay);
resultPath.lineTo(ax, ay);
resultPath.lineTo(length * Math.cos(radC) + ax, length * Math.sin(radC) + ay);
return (Path2D.Double) resultPath;
}
Although there have been some hiccups in the question, and the code provided so far looks questionable, the core of the question as it stands now is quite interesting...
There are different options for solving this. From the images that you provided so far, it looks like the circles always have the same size, which makes things far simpler. For circles with different sizes, you'd really have to compute the tangents of the circles, in the desired direction, mutually considering the radius of the other circle. Of course, this is possible, but a bit less trivial.
For the case that you have equally-sized circles, you can
Compute the difference of the centers of two circles
Divide this by the distance, to obtain the (normalized) direction
Rotate this direction by 90°
Scale the rotated direction vector by the radius
Add the scaled and rotated vector to the circle center
This will yield one endpoint of such a line. The rotation about 90° can be done once in clockwise and once in counterclockwise direction, to obtain the "upper" and "lower" endpoint for the line, respectively.
Image was updated with the EDIT, see below
The actual computation is done in the computeLine method of the MCVE below. Note that this example uses the "simple" approach, although it uses circles of slightly different sizes. The effect is that, when the difference between the sizes of two circles is too large (compared to the distance between the circles, basically), then the lines may slightly intersect the circles. But the solution should be a reasonable trade-off between simplicity and general applicability. Particularly, for equally-sized circles, there will be no intersections at all.
Code was updated with the EDIT, see below
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LinesAtCirclesTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel linesAtCirclesTestPanel = new LinesAtCirclesTestPanel();
f.getContentPane().add(linesAtCirclesTestPanel);
f.setSize(400,400);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class LinesAtCirclesTestPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D draggedCenter;
private List<Point2D> centers = new ArrayList<Point2D>();
private List<Double> radii = new ArrayList<Double>();
public LinesAtCirclesTestPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
addCircle(100, 100, 30);
addCircle(200, 300, 50);
addCircle(300, 200, 40);
}
private void addCircle(double x, double y, double radius)
{
centers.add(new Point2D.Double(x,y));
radii.add(radius);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (int i=0; i<centers.size(); i++)
{
Point2D center0 = centers.get(i);
double radius0 = radii.get(i);
Shape ellipse = new Ellipse2D.Double(
center0.getX() - radius0, center0.getY() - radius0,
radius0 + radius0, radius0 + radius0);
g.setColor(Color.LIGHT_GRAY);
g.fill(ellipse);
g.setColor(Color.BLACK);
g.draw(ellipse);
}
g.setColor(Color.RED);
for (int i=0; i<centers.size() - 1; i++)
{
Point2D center0 = centers.get(i);
double radius0 = radii.get(i);
Point2D center1 = centers.get(i+1);
double radius1 = radii.get(i+1);
g.draw(createArrow(computeLine(center0, radius0, center1, radius1, true)));
g.draw(createArrow(computeLine(center0, radius0, center1, radius1, false)));
}
}
private static Shape createArrow(Line2D line)
{
double dx = line.getX2() - line.getX1();
double dy = line.getY2() - line.getY1();
double angleToX = Math.atan2(dy, dx);
final double angleRad = Math.toRadians(30);
final double headLength = 20.0f;
double dxL = Math.cos(Math.PI + angleToX + angleRad) * headLength;
double dyL = Math.sin(Math.PI + angleToX + angleRad) * headLength;
double dxR = Math.cos(Math.PI + angleToX - angleRad) * headLength;
double dyR = Math.sin(Math.PI + angleToX - angleRad) * headLength;
Path2D arrow = new Path2D.Double();
arrow.moveTo(line.getX1(), line.getY1());
arrow.lineTo(line.getX2(), line.getY2());
arrow.lineTo(line.getX2() + dxL, line.getY2() + dyL);
arrow.moveTo(line.getX2(), line.getY2());
arrow.lineTo(line.getX2() + dxR, line.getY2() + dyR);
return arrow;
}
private static Line2D computeLine(
Point2D center0, double radius0,
Point2D center1, double radius1,
boolean upper)
{
double dx = center1.getX() - center0.getX();
double dy = center1.getY() - center0.getY();
double invLength = 1.0 / Math.hypot(dx, dy);
double dirX = dx * invLength;
double dirY = dy * invLength;
double rotDirX = dirY;
double rotDirY = -dirX;
if (upper)
{
rotDirX = -dirY;
rotDirY = dirX;
}
double x0 = center0.getX() + rotDirX * radius0;
double y0 = center0.getY() + rotDirY * radius0;
double x1 = center1.getX() + rotDirX * radius1;
double y1 = center1.getY() + rotDirY * radius1;
if (upper)
{
return new Line2D.Double(x1, y1, x0, y0);
}
return new Line2D.Double(x0, y0, x1, y1);
}
#Override
public void mousePressed(MouseEvent e)
{
draggedCenter = null;
for (int i=0; i<centers.size(); i++)
{
Point2D center = centers.get(i);
double radius = radii.get(i);
if (e.getPoint().distance(center) < radius)
{
draggedCenter = center;
}
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedCenter = null;
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedCenter == null)
{
return;
}
draggedCenter.setLocation(e.getPoint());
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
// Not used
}
#Override
public void mouseClicked(MouseEvent e)
{
// Not used
}
#Override
public void mouseEntered(MouseEvent e)
{
// Not used
}
#Override
public void mouseExited(MouseEvent e)
{
// Not used
}
}
EDIT in response to the comment:
The original code computed Line2D objects. Creating an arrow from a line is, in the simplest case, basically done with a bit of trigonometry, and many resources exist for this on the web.
In response to the comment, I extended the example to show simple arrows, as depicted in the above image.
However, when taking a closer look at this, one may notice several degrees of freedom for such an arrow:
Should the head length be absolute or relative to the arrow?
Should the head width be absolute or relative to the arrow?
(Or: What should be the angle of the arrow head?)
Should the arrow head be filled, or consist of lines?
Should the "trunk" of the arrow be a single line, or an outline shape?
What should be the width of the trunk?
...
In order to cover some of these degrees of freedom, I created an ArrowCreator class a while ago, and there's also a sample showing how it may be used.
I'm making an application about space physics, so I do lots with orbits. Naturally, I encounter the Ellipse2D.Double to draw my orbits on the screen.
Whenever my JPanel refreshes, I draw the orbit of a body using an Ellipse2D, as well as the body itself with a different method.
Essentially, I discovered that when numbers get very large (whether it be the size of the orbits get large or the visualization is zoomed in very far), the position of the body and the Ellipse2D do not line up.
I calculate the position of the body using a conversion from polar coordinates to rectangular coordinates, and I leave the math for the Ellipse2D up to the geom package.
Take a look at this code sample. It's the most self-contained version of my problem that I can make, since scale of the circle has to be very large:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.math.BigDecimal;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class EllipseDemo extends JPanel {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.add(new EllipseDemo());
frame.setVisible(true);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// These values allow for a very zoomed in view of a piece of the circle
BigDecimal[] circleCenter = { new BigDecimal(-262842.5), new BigDecimal(-93212.8) };
BigDecimal circleRadius = new BigDecimal(279081.3);
// Draw the circle at the given center, with the given width and height
// x = centerx - radius, y = centery - radius, w = h = radius * 2
g2d.draw(new Ellipse2D.Double(circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
// Get a rectangular conversion of a point on the circle at this angle
BigDecimal angle = new BigDecimal(0.34117696217);
BigDecimal[] rectangular = convertPolarToRectangular(new BigDecimal[] {
circleRadius, angle });
// Draw a line from the center of the circle to the point
g2d.draw(new Line2D.Double(circleCenter[0].doubleValue(), circleCenter[1].doubleValue(),
circleCenter[0].add(rectangular[0]).doubleValue(), circleCenter[1]
.add(rectangular[1]).doubleValue()));
}
public BigDecimal[] convertPolarToRectangular(BigDecimal[] polar) {
BigDecimal radius = polar[0];
BigDecimal angle = polar[1];
BigDecimal x = radius.multiply(new BigDecimal(Math.cos(angle.doubleValue())));
BigDecimal y = radius.multiply(new BigDecimal(Math.sin(angle.doubleValue())));
return new BigDecimal[] { x, y };
}
}
The code above essentially draws a circle on the screen very far away with a large radius. I've picked the dimension so that a piece of the circle is visible in the small window.
Then it draws a line from the center of the circle to a point on the circle that's visible in the window: I picked an angle that was visible on the window and used geometry to convert that angle and the radius of the circle into rectangular coordinates.
This is what the program displays:
Notice that the line doesn't actually end up touching the ellipse. Now, I decided I had to find out whether it was the point I calculated or the ellipse that were incorrect. I did the math on my calculator, and found that the line was correct, and the ellipse incorrect:
Considering that the calculator is probably not wrong, I am led to believe the Ellipse2D is not drawing correctly. However, I tried many other angles, and this is the pattern I found:
And that leads me to believe the calculations are somehow wrong.
So that's my problem. Should I be using something other than Ellipse2D? Maybe Ellipse2D is not accurate enough? I used BigDecimals in my code sample because I thought it would give me more precision - is that the wrong approach? My ultimate goal is to be able to calculate the rectangular position of a point on an ellipse at a specific angle.
Thanks in advance.
You see this error because Ellipse2D is approximated by four cubic curves. To make sure just take a look at its path iterator defining shape border: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/geom/EllipseIterator.java#187
To improve quality we should approximate ellipse by higher number of cubic curves. Here is an extention of standard java implementation with changeable number of segments:
class BetterEllipse extends Ellipse2D.Double {
private int segments;
public BetterEllipse(int segments, double x, double y, double w, double h) {
super(x, y, w, h);
this.segments = segments;
}
public int getSegments() {
return segments;
}
#Override
public PathIterator getPathIterator(final AffineTransform affine) {
return new PathIterator() {
private int index = 0;
#Override
public void next() {
index++;
}
#Override
public int getWindingRule() {
return WIND_NON_ZERO;
}
#Override
public boolean isDone() {
return index > getSegments() + 1;
}
#Override
public int currentSegment(double[] coords) {
int count = getSegments();
if (index > count)
return SEG_CLOSE;
BetterEllipse ellipse = BetterEllipse.this;
double x = ellipse.getCenterX() + Math.sin(2 * Math.PI * index / count) * ellipse.getWidth() / 2;
double y = ellipse.getCenterY() + Math.cos(2 * Math.PI * index / count) * ellipse.getHeight() / 2;
if (index == 0) {
coords[0] = x;
coords[1] = y;
if (affine != null)
affine.transform(coords, 0, coords, 0, 1);
return SEG_MOVETO;
}
double x0 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 2) / count) * ellipse.getWidth() / 2;
double y0 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 2) / count) * ellipse.getHeight() / 2;
double x1 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index - 1) / count) * ellipse.getWidth() / 2;
double y1 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index - 1) / count) * ellipse.getHeight() / 2;
double x2 = x;
double y2 = y;
double x3 = ellipse.getCenterX() + Math.sin(2 * Math.PI * (index + 1) / count) * ellipse.getWidth() / 2;
double y3 = ellipse.getCenterY() + Math.cos(2 * Math.PI * (index + 1) / count) * ellipse.getHeight() / 2;
double x1ctrl = x1 + (x2 - x0) / 6;
double y1ctrl = y1 + (y2 - y0) / 6;
double x2ctrl = x2 + (x1 - x3) / 6;
double y2ctrl = y2 + (y1 - y3) / 6;
coords[0] = x1ctrl;
coords[1] = y1ctrl;
coords[2] = x2ctrl;
coords[3] = y2ctrl;
coords[4] = x2;
coords[5] = y2;
if (affine != null)
affine.transform(coords, 0, coords, 0, 3);
return SEG_CUBICTO;
}
#Override
public int currentSegment(float[] coords) {
double[] temp = new double[6];
int ret = currentSegment(temp);
for (int i = 0; i < coords.length; i++)
coords[i] = (float)temp[i];
return ret;
}
};
}
}
And here is how you can use it in your code instead of standard one (I use 100 segments here):
g2d.draw(new BetterEllipse(100, circleCenter[0].subtract(circleRadius).doubleValue(),
circleCenter[1].subtract(circleRadius).doubleValue(), circleRadius.multiply(new BigDecimal(2)).doubleValue(),
circleRadius.multiply(new BigDecimal(2)).doubleValue()));
I have a Shape. I'm basically trying to split an area into two areas using a segment as the bisection.
public Shape divide(Shape a, Point2D p1, Point2D p2) {
Shape str = new BasicStroke().createStrokedShape(new Line2D.Double(p1,p2));
Shape line = new Shape(str);
Shape temp = a;
line.intersect(temp);
temp.exclusiveOr(line);
// temp is the shape with the line intersecting it
AffineTransform t = new AffineTransform();
double angle = Math.atan2(p2.getY() - p1.getY(), p2.getX() - p1.getX());
t.rotate(angle, p1.getX(), p1.getY());
temp = temp.createTransformedArea(t);
return Shape ;
}
I want to bisect the shape into two using the segment, but not sure how to go about it, I was looking at the intersection methods:
http://docs.oracle.com/javase/7/docs/api/java/awt/geom/Area.html but still not sure how to get two Areas from one. I'm hoping to return something like:
return firstHalf secondHalf;
I'd do it something like this. Note the code has a bug for when the point that start out to the right and lower ends up to the left of the upper left point. Left as an exercise for the user.
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class SplitArea {
int s = 100;
JPanel gui = new JPanel(new BorderLayout());
BufferedImage[] images = new BufferedImage[4];
Point p1 = new Point(s / 4, s / 4);
Point p2 = new Point(s * 3 / 4, s * 3 / 4);
Ellipse2D ellipse = new Ellipse2D.Float(
s / 5, s / 5, s * 3 / 5, s * 3 / 5);
Rectangle2D bg = new Rectangle2D.Float(0, 0, s, s);
SplitArea() {
JToolBar tb = new JToolBar();
gui.add(tb, BorderLayout.PAGE_START);
final JToggleButton tob = new JToggleButton("Primary Point");
tb.add(tob);
JPanel view = new JPanel(new GridLayout(1, 0, 4, 4));
gui.add(view, BorderLayout.CENTER);
for (int ii = 0; ii < images.length; ii++) {
BufferedImage bi = new BufferedImage(
s, s, BufferedImage.TYPE_INT_RGB);
images[ii] = bi;
JLabel l = new JLabel(new ImageIcon(bi));
if (ii == 0) {
l.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (tob.isSelected()) {
p1 = e.getPoint();
} else {
p2 = e.getPoint();
}
drawImages();
}
});
}
view.add(l);
}
drawImages();
}
public final void drawImages() {
Graphics2D g;
// image 0
g = images[0].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipse);
g.setColor(Color.WHITE);
g.draw(ellipse);
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
int xDiff = p1.x - p2.x;
int yDiff = p1.y - p2.y;
Point2D xAxis;
Point2D xSAxis;
if (xDiff == 0) {
xAxis = new Point2D.Double(p1.x, 0);
xSAxis = new Point2D.Double(p1.x, s);
} else if (yDiff == 0) {
xAxis = new Point2D.Double(0, p1.y);
xSAxis = new Point2D.Double(s, p1.y);
} else {
System.out.println("Not vertical or horizontal!");
// will throw a NaN if line is vertical
double m = (double) yDiff / (double) xDiff;
System.out.println("m: " + m);
double b = (double) p1.y - (m * (double) p1.x);
System.out.println("b: " + b);
// crosses x axis at..
xAxis = new Point2D.Double(0d, b);
double pointS = (s - b) / m;
xSAxis = new Point2D.Double(pointS, s);
}
// image 1
g = images[1].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipse);
g.setColor(Color.WHITE);
g.draw(ellipse);
g.setColor(Color.YELLOW);
System.out.println(xAxis);
System.out.println(xSAxis);
g.drawLine(
(int) xAxis.getX(), (int) xAxis.getY(),
(int) xSAxis.getX(), (int) xSAxis.getY());
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
// image 2
g = images[1].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipse);
g.setColor(Color.WHITE);
g.draw(ellipse);
g.setColor(Color.YELLOW);
System.out.println(xAxis);
System.out.println(xSAxis);
g.drawLine(
(int) xAxis.getX(), (int) xAxis.getY(),
(int) xSAxis.getX(), (int) xSAxis.getY());
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
// split the regions
Rectangle2D.Double all = new Rectangle2D.Double(0, 0, s, s);
Area a1 = new Area(all);
Area a2 = new Area(all);
GeneralPath aPart = new GeneralPath();
aPart.moveTo(0, 0);
aPart.lineTo(0, s);
aPart.lineTo(xSAxis.getX(), xSAxis.getY());
aPart.lineTo(xAxis.getX(), xAxis.getY());
aPart.closePath();
a1.subtract(new Area(aPart));
a2.subtract(a1);
Area ellipsePartA = new Area(ellipse);
ellipsePartA.subtract(a1);
Area ellipsePartB = new Area(ellipse);
ellipsePartB.subtract(a2);
// image 3
g = images[2].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipsePartA);
g.setColor(Color.WHITE);
g.draw(ellipsePartA);
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
// image 4
g = images[3].createGraphics();
g.setColor(Color.BLACK);
g.fill(bg);
g.setColor(Color.CYAN);
g.fill(ellipsePartB);
g.setColor(Color.WHITE);
g.draw(ellipsePartB);
g.setColor(Color.red);
drawPoint(g, p1);
drawPoint(g, p2);
g.dispose();
gui.repaint();
}
public final void drawPoint(Graphics g, Point2D p) {
g.setColor(new Color(255, 0, 0, 128));
int x = (int) p.getX();
int y = (int) p.getY();
g.drawLine(x - 1, y, x - 5, y);
g.drawLine(x + 1, y, x + 5, y);
g.drawLine(x, y - 1, x, y - 5);
g.drawLine(x, y + 1, x, y + 5);
}
public Area[] split(Area a, Point2D p1, Point2D p2) {
Shape str = new BasicStroke().createStrokedShape(new Line2D.Double(p1, p2));
Area line = new Area(str);
Area temp = a;
line.intersect(temp);
temp.exclusiveOr(line);
// temp is the shape with the line intersecting it
Area[] areas = {new Area(temp)};
return areas;
}
public JComponent getGui() {
return gui;
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
SplitArea sa = new SplitArea();
JOptionPane.showMessageDialog(null, sa.getGui());
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Here is another https://stackoverflow.com/help/mcve (I started this "yesterday" at ~3:00 am, obviously Andrew Thompson is in a different time zone ;-))
The basic idea here is as follows:
The two given points define a line. That is, an infinite line, and not only a line segment. The corner points of the bounding box of the object are projected on this line and its perpendicular. This gives (an upper bound of) the extent of the object along these lines. These upper bounds can be used to define the "minimum half-spaces" above and below the line that are required to cover the respective half of the object. These half-spaces can then be intersected with the object to obtain the desired results.
The split method in this example receives a Graphics2D parameter. This is only used for "debugging" - that is, to show the intermediate results (extents, half-spaces) that are computed, and a preview of the final results. This Graphics g parameter (and the corresponding debugging output) can simply be removed (but it might also help to show the idea of the approach).
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeSplit
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ShapeSplitPanel());
f.setSize(1100,600);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ShapeSplitPanel extends JPanel implements MouseMotionListener
{
private Shape inputShape = new Ellipse2D.Double(300,200,200,300);
private Point2D point0 = new Point2D.Double(200,300);
private Point2D point1 = new Point2D.Double(600,400);
ShapeSplitPanel()
{
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.BLUE);
g.fill(inputShape);
g.setColor(Color.BLACK);
g.draw(new Line2D.Double(point0, point1));
g.fill(new Ellipse2D.Double(
point0.getX() - 3, point0.getY()-3, 6, 6));
g.fill(new Ellipse2D.Double(
point1.getX() - 3, point1.getY()-3, 6, 6));
split(new Area(inputShape), point0, point1, g);
}
private static Area[] split(Area a, Point2D p0, Point2D p1, Graphics2D g)
{
// Compute the direction of the line (L)
// and its perpendicular (P)
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double length = Math.hypot(dx, dy);
double dirLx = dx / length;
double dirLy = dy / length;
double dirPx = -dirLy;
double dirPy = dirLx;
// Compute the minimum and maximum of all dot
// products that describe the distance of the
// projection of the corner points of the
// bounding box on on the line (L) and its
// perpendicular (P). These are upper limits
// for the extents of the object along these
// directions
double minDotL = Double.MAX_VALUE;
double maxDotL = -Double.MAX_VALUE;
double minDotP = Double.MAX_VALUE;
double maxDotP = -Double.MAX_VALUE;
Rectangle2D bounds = a.getBounds2D();
for (int i=0; i<4; i++)
{
Point2D corner = getCorner(bounds, i);
double pdx = corner.getX() - p0.getX();
double pdy = corner.getY() - p0.getY();
double dotL = dirLx * pdx + dirLy * pdy;
minDotL = Math.min(minDotL, dotL);
maxDotL = Math.max(maxDotL, dotL);
double dotP = dirPx * pdx + dirPy * pdy;
minDotP = Math.min(minDotP, dotP);
maxDotP = Math.max(maxDotP, dotP);
}
// Compute the start- and end points of
// the line segments describing the
// extent of the bounds along the line
// and the perpendicular
Point2D extentLmin = new Point2D.Double(
p0.getX() + minDotL * dirLx,
p0.getY() + minDotL * dirLy);
Point2D extentLmax = new Point2D.Double(
p0.getX() + maxDotL * dirLx,
p0.getY() + maxDotL * dirLy);
Point2D extentPmin = new Point2D.Double(
p0.getX() + minDotP * dirPx,
p0.getY() + minDotP * dirPy);
Point2D extentPmax = new Point2D.Double(
p0.getX() + maxDotP * dirPx,
p0.getY() + maxDotP * dirPy);
// Compute the two rectangles that cover
// each half of the object based on
// the given line
Path2D half0 = new Path2D.Double();
half0.moveTo(extentLmin.getX(), extentLmin.getY());
half0.lineTo(
extentLmin.getX() + minDotP * dirPx,
extentLmin.getY() + minDotP * dirPy);
half0.lineTo(
extentLmax.getX() + minDotP * dirPx,
extentLmax.getY() + minDotP * dirPy);
half0.lineTo(extentLmax.getX(), extentLmax.getY());
half0.closePath();
Path2D half1 = new Path2D.Double();
half1.moveTo(extentLmin.getX(), extentLmin.getY());
half1.lineTo(
extentLmin.getX() + maxDotP * dirPx,
extentLmin.getY() + maxDotP * dirPy);
half1.lineTo(
extentLmax.getX() + maxDotP * dirPx,
extentLmax.getY() + maxDotP * dirPy);
half1.lineTo(extentLmax.getX(), extentLmax.getY());
half1.closePath();
// Compute the resulting areas by intersecting
// the original area with both halves
Area a0 = new Area(a);
a0.intersect(new Area(half0));
Area a1 = new Area(a);
a1.intersect(new Area(half1));
// Debugging output
if (g != null)
{
g.setColor(Color.GRAY);
g.draw(bounds);
g.setColor(Color.RED);
g.draw(new Line2D.Double(extentLmin, extentLmax));
g.setColor(Color.GREEN);
g.draw(new Line2D.Double(extentPmin, extentPmax));
g.setColor(Color.YELLOW.darker());
g.draw(half0);
g.setColor(Color.MAGENTA);
g.draw(half1);
g.setColor(Color.BLUE);
g.fill(AffineTransform.getTranslateInstance(400, -20).
createTransformedShape(a0));
g.setColor(Color.BLUE);
g.fill(AffineTransform.getTranslateInstance(400, +20).
createTransformedShape(a1));
}
return new Area[] { a0, a1 };
}
private static Point2D getCorner(Rectangle2D r, int corner)
{
switch (corner)
{
case 0: return new Point2D.Double(r.getMinX(), r.getMinY());
case 1: return new Point2D.Double(r.getMinX(), r.getMaxY());
case 2: return new Point2D.Double(r.getMaxX(), r.getMaxY());
case 3: return new Point2D.Double(r.getMaxX(), r.getMinY());
}
return null;
}
#Override
public void mouseDragged(MouseEvent e)
{
point1.setLocation(e.getPoint());
repaint();
}
#Override
public void mouseMoved(MouseEvent e)
{
}
}
EDIT An aside: Technically, it could be easier (or even more elegant) to transform the original shape and the line so that the line matches the x-axis, then defining the half-spaces to be clipped against (which in this case could be simple Rectangle2Ds), and transforming the clipped results back into the original orientation. But I wanted to compute it "in-place", without having to create many transformed shapes.
EDIT2: Another snippet for the comment, to be inserted directly before the // Debugging output
AffineTransform t = new AffineTransform();
double angle = Math.atan2(p1.getY() - p0.getY(), p1.getX() - p0.getX());
t.rotate(-angle, p0.getX(), p0.getY());
a0 = a0.createTransformedArea(t);
a1 = a1.createTransformedArea(t);
EDIT3 The second approach, only the relevant method this time
private static Area[] split(Area a, Point2D p0, Point2D p1, Graphics2D g)
{
// Compute the angle of the line to the x-axis
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double angleRadToX = Math.atan2(dy, dx);
// Align the area so that the line matches the x-axis
AffineTransform at = new AffineTransform();
at.rotate(-angleRadToX);
at.translate(-p0.getX(), -p0.getY());
Area aa = a.createTransformedArea(at);
// Compute the upper and lower halves that the area
// has to be intersected with
Rectangle2D bounds = aa.getBounds2D();
double half0minY = Math.min(0, bounds.getMinY());
double half0maxY = Math.min(0, bounds.getMaxY());
Rectangle2D half0 = new Rectangle2D.Double(
bounds.getX(), half0minY,
bounds.getWidth(), half0maxY-half0minY);
double half1minY = Math.max(0, bounds.getMinY());
double half1maxY = Math.max(0, bounds.getMaxY());
Rectangle2D half1 = new Rectangle2D.Double(
bounds.getX(), half1minY,
bounds.getWidth(), half1maxY-half1minY);
// Compute the resulting areas by intersecting
// the original area with both halves, and
// transform them back to their initial position
Area a0 = new Area(aa);
a0.intersect(new Area(half0));
Area a1 = new Area(aa);
a1.intersect(new Area(half1));
try
{
at.invert();
}
catch (NoninvertibleTransformException e)
{
// Always invertible
}
a0 = a0.createTransformedArea(at);
a1 = a1.createTransformedArea(at);
// Debugging output
if (g != null)
{
g.setColor(Color.GRAY);
g.draw(bounds);
g.setColor(Color.RED);
g.draw(aa);
g.setColor(Color.YELLOW.darker());
g.draw(half0);
g.setColor(Color.MAGENTA);
g.draw(half1);
g.setColor(Color.BLUE.darker());
g.fill(AffineTransform.getTranslateInstance(400, -20).
createTransformedShape(a0));
g.setColor(Color.BLUE.brighter());
g.fill(AffineTransform.getTranslateInstance(400, +20).
createTransformedShape(a1));
}
return new Area[] { a0, a1 };
}
Interesting question.
There are no methods that help you with this directly, but by calculating the bounding rectangle and intersecting with two opposing rectangles from your dividing line, you should be able to create such a method.
The general idea is to
Find the bounding rectangle of your original area: either getBounds() or getBounds2D().
Calculate two rectangles from your line that overlaps your area on both sides of the line. When doing this you will have to take several special cases into account (like is the line long enough, does it intersect the area at all, does the rectangles completely overlap each side of the original area, etc). The size of the rectangles should be decided by the bounding rectangle of your original area.
Get the two areas by intersecting each of the two rectangles with your original area, i.e. by using the intersect() method
I'm trying to draw to filled circles, centered at random locations, with a line connecting the circles. The distance between the to centers is displayed on the line and whenever your resize the frame, the circles are redisplayed in new random locations.
I'm stuck in how to display the distance?
Any help is appreciated and thx for advance.
This's the code (what i managed to do):
public class Test extends JFrame {
public Test() {
add(new LineConnectingTheTwoCircles());
}
// Panel class
class LineConnectingTheTwoCircles extends JPanel {
//Default constructor
LineConnectingTheTwoCircles() {
}
/* Override paintComponent (getting access to the panel's Graphics
class) */
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int radius = 15;
// getting coordinates of circle 1
int x1 = (int) (Math.random() * (getWidth()));
int y1 = (int) (Math.random() * (getHeight()));
// getting coordinates of circle 2
int x2 = (int) (Math.random() * (getWidth()));
int y2 = (int) (Math.random() * (getWidth()));
// Setting color and drawing a filled circles (1 & 2)
g.setColor(Color.BLUE);
g.fillOval(x1 - radius, y1 - radius, 2 * radius, 2 * radius);
g.drawString("1", x1 - 25, y1);
g.setColor(Color.RED);
g.fillOval(x2 - radius, y2 - radius, 2 * radius, 2 * radius);
g.drawString("2", x2 - 25, y2);
connectingTheTwoCircles(g, x1, y1, x2, y2);
}
// Connecting the two circles from the center
private void connectingTheTwoCircles(Graphics g, int x1, int y1,
int x2, int y2) {
//Distance between the circles centered
double D = Math.sqrt((Math.pow((y2 - y1), 2))
+ (Math.pow((x2 - x1), 2)));
//Getting the coordinates for the line l
int x11 = x1;
int y11 = y1;
int x21 = x2;
int y21 = y2;
g.setColor(Color.BLACK);
g.drawLine(x11, y11, x21, y21);
}
public double getDistance(double x1, double y1, double x2, double y2) {
return Math.sqrt((Math.pow((y2 - y1), 2))
+ (Math.pow((x2 - x1), 2)));
}
}
public static void main(String[] args) {
// Frame declaration
Test frame = new Test();
/*
* Invoking some methods, to set a title on the title bar, to specifier
* the size of the frame to centre it on the screen, to tell the program
* to terminate when the frame is closed and finally to display it
*/
frame.setTitle("This is a test");
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Try next code draws distance at line center
double distance = getDistance(x11, y11, x21, y21);
g.drawString(distance+" ",
x11> x21 ? x21 + (x11-x21)/2 : x11 + (x21 - x11)/2 ,
y11> y21 ? y21 + (y11-y21)/2 : y11 + (y21 - y11)/2 );