Why does this code:
Line2D line1 = new Line2D.Double(464.9298111721873, 103.78661133348942, 684.8391765253534, -155.44752172931908);
Line2D line2 = new Line2D.Double(414.16903384086487, 163.62456359144306, 393.52528378472925, 187.95988300984624);
line1.intersectsLine(line2);
return true?
Clearly, x coordinates of lines are way apart, and they do not intersect. I drew them onto a swing panel, and they are apart, but look like they are collinear. Is this the problem?
I tried testing on simple collinear lines (like (1, 3, 4, 3), (6, 3, 8, 3)), and it seemes to work fine.
Java Docs say that the method of the Line2D class:
public boolean intersects(double x, double y, double w, double h)
tests if the interior of the Shape intersects the interior of a specified rectangular area. It uses the Shape.intersects() method for this, however the calculations to accurately determine this intersection are very expensive.
This means that for some Shapes this method might return true even though the rectangular area does not intersect the Shape.
Although you are using the method itersectsLine(), both intersects() and intersectsLine() use the same expensive method underneath the surface. The method is called linesIntersect() and is specified here on line 298
The Area class performs more accurate computations of geometric intersection than most Shape objects and therefore can be used if a more precise answer is required. For instance:
boolean intersectionExists(Shape shape1, Shape shape2) {
Area area1 = new Area(shape1);
area1.intersect(new Area(shape2));
return !area1.isEmpty();
}
Tested using your values:
public static void main(String[] args) {
Line2D line1 = new Line2D.Double(464.9298111721873, 103.78661133348942, 684.8391765253534, -155.44752172931908);
Line2D line2 = new Line2D.Double(414.16903384086487, 163.62456359144306, 393.52528378472925, 187.95988300984624);
System.out.println("Lines intersect? " + intersectionExists(line1, line2));
}
Output:
Lines intersect? false
Related
I'm working on a collision system for my game, however I can't get it to work, if I add more than one wall (which is the object I'm rendering) the collision system doesn't work and I can get through the block.
However if I leave only one wall the collision works correctly, or if at the end of the loop I add a break;
the collision works but only on the first wall of the map, the others don't get the collision.
Would anyone know how to solve this? I've been trying to solve it for 2 days and I couldn't.
public boolean checkCollisionWall(int xnext, int ynext){
int[] xpoints1 = {xnext+3,xnext+3,xnext+4,xnext+3,xnext+3,xnext+4,xnext+10,xnext+11,xnext+11,xnext+10,xnext+11,xnext+11};
int[] ypoints1 = {ynext+0,ynext+8,ynext+9,ynext+11,ynext+12,ynext+15,ynext+15,ynext+12,ynext+11,ynext+9,ynext+8,ynext+0};
int npoints1 = 12;
Polygon player = new Polygon(xpoints1,ypoints1,npoints1);
Area area = new Area(player);
for(int i = 0; i < Game.walls.size(); i++){
Wall atual = Game.walls.get(i);
int[] xpoints2 = {atual.getX(),atual.getX(),atual.getX()+16,atual.getX()+16};
int[] ypoints2 = {atual.getY(),atual.getY()+16,atual.getY()+16,atual.getY()};
int npoints2 = 4;
Polygon Wall = new Polygon(xpoints2,ypoints2,npoints2);
area.intersect(new Area(Wall));
if(area.isEmpty()){
return true;
}
}
return false;
}
I'm pretty sure the problem is this call:
area.intersect(new Area(Wall));
Here's the JavaDoc for that method:
public void intersect(Area rhs)
Sets the shape of this Area to the intersection of its current shape
and the shape of the specified Area. The resulting shape of this Area
will include only areas that were contained in both this Area and also
in the specified Area.
So area, which represents the shape of your player, is going to be modified by each test with a wall, which is why it's only working with one wall.
You could fix the issue by simply making the player Area the argument of the call, as in:
Area wallArea = new Area(Wall);
wallArea.intersect(area);
if(wallArea.isEmpty()){
return true;
}
By the way, this logic is reversed isn't it. Don't you want to check that the resulting area is not empty, i.e. there was an overlap between the player and the wall?
The other option, if each Wall is actually a rectangle, would be to use the this Area method instead:
public boolean intersects(double x,
double y,
double w,
double h)
Tests if the interior of the Shape intersects the interior of a
specified rectangular area. The rectangular area is considered to
intersect the Shape if any point is contained in both the interior of
the Shape and the specified rectangular area.
Something like this:
if(area.intersects(atual.getX(), atual.getY(), 16, 16)) {
return true;
}
As this avoids the creation of an Area object for each wall, and the intersection test is going to be much simpler, and faster.
I need to convert a java.awt.geom.Area or java.awt.Shape to java.awt.Polygon. What I know about the are is: isSingular = true, isPolygonal = true. So I think a polygon shuld be able to describe the same area.
I'm not sure that it is worth converting, because Polygon is an old Java 1.0 class that can store only integer coordinates, so you might lose some precision.
Anyway, you can get a PathIterator from the Shape, and as you iterate it, add new points to a Polygon:
public static void main(String[] args) {
Area a = new Area(new Rectangle(1, 1, 5, 5));
PathIterator iterator = a.getPathIterator(null);
float[] floats = new float[6];
Polygon polygon = new Polygon();
while (!iterator.isDone()) {
int type = iterator.currentSegment(floats);
int x = (int) floats[0];
int y = (int) floats[1];
if(type != PathIterator.SEG_CLOSE) {
polygon.addPoint(x, y);
System.out.println("adding x = " + x + ", y = " + y);
}
iterator.next();
}
}
EDIT As Bill Lin commented, this code may give you a wrong polygon if the PathIterator describes multiple subpaths (for example in the case of an Area with holes). In order to take this into account, you also need to check for PathIterator.MOVETO segments, and possibly create a list of polygons.
In order to decide which polygons are holes, you could calculate the bounding box (Shape.getBounds2D()), and check which bounding box contains the other. Note that the getBounds2D API says that "there is no guarantee that the returned Rectangle2D is the smallest bounding box that encloses the Shape, only that the Shape lies entirely within the indicated Rectangle2D", but in my experience for polygonal shapes it would be the smallest, and anyway it is trivial to calculate the exact bounding box of a polygon (just find the smallest and biggest x and y coordinates).
There is a Rectangle2D.Double object named rect2d and a Line2D.Double object named line2d.
Please consider the following situation:
Question 1:
What will this line of code return?
boolean intersect = line2d.getBounds2D().intersects(rect2d);
Question 2:
The width/thickness of line2d is 1 pixel. It's height/length is 20 pixels.
What would be the width and height values of the Rectangle2D object returned by line2d.getBounds2D()?
It will return:
false
java.awt.geom.Rectangle2D$Double[x=10.0,y=10.0,w=0.0,h=20.0]
With this code:
Line2D.Double line2d = new Line2D.Double(10, 10, 10, 30);
Rectangle2D.Double rect2d = new Rectangle2D.Double(0, 0, 100, 100);
boolean intersect = line2d.getBounds2D().intersects(rect2d);
System.out.println(intersect);
System.out.println(line2d.getBounds2D());
Although, it's useless to say that a line is 1 pixel, because it can't be other way using Line2D.Double.
Indeed, for Java, this vertical line as a 0-pixel width boundary, so that's why it will never intersect with any other shape. With a non-vertical and non-horizontal line, its bounds intersect.
Do not use bounds to compute intersection, but directly the Shape:
boolean intersect = line2d.intersects(rect2d);
You may check RectangularShape.intersects() Javadoc for more information on how this method computes intersection.
From the docs, here and here, the first would return false and the second a bounding box at least the size of the dimensions of the line.
When using the Graphics2D scale() function with two different parameters (scaling by different ratios in x- and y-direction), everything drawn later on this Graphics2D object is scaled too. This has the strange effect that lines drawn in one direction are thicker than those in another direction. The following program produces this effect, it shows this window:
public class StrokeExample extends JPanel {
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
g.setStroke(new BasicStroke(0.2f));
int height = getHeight();
int width = getWidth();
g.scale(width/7.0, height/4.0);
g.setColor(Color.BLACK);
g.draw(new Rectangle( 2, 1, 4, 2));
}
public static void main(String[] params) {
EventQueue.invokeLater(new Runnable(){public void run() {
StrokeExample example = new StrokeExample();
JFrame f = new JFrame("StrokeExample");
f.setSize(100, 300);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(example);
f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
f.setVisible(true);
}});
}
}
I'm using this coordinate transform to avoid having to manually transform my application model coordinates (the (2,1, 2,4) in this example) to screen (or component) pixel coordinates, but I don't want this stroke distortion. In other words, I want to have all lines the same width, independent of current x- and y-scale-factors.
I know what produces this effect (the Stroke object creates a stroked shape of the rectangle to be painted in user coordinates, which then are translated to screen coordinates), but I'm not sure on how to solve this.
Should I create a new Stroke implementation which strokes Shapes differently in X- and Y-direction (thereby undoing the distortion here)? (Or does anyone already knows such an implementation?)
Should I transform my shapes to screen coordinates and stroke there?
Any other (better) ideas?
Turns out my question was not so horrible difficult, and that my two ideas given in the question are actually the same idea. Here is a TransformedStroke class which implements a distorted Stroke by transforming the Shape.
import java.awt.*;
import java.awt.geom.*;
/**
* A implementation of {#link Stroke} which transforms another Stroke
* with an {#link AffineTransform} before stroking with it.
*
* This class is immutable as long as the underlying stroke is
* immutable.
*/
public class TransformedStroke
implements Stroke
{
/**
* To make this serializable without problems.
*/
private static final long serialVersionUID = 1;
/**
* the AffineTransform used to transform the shape before stroking.
*/
private AffineTransform transform;
/**
* The inverse of {#link #transform}, used to transform
* back after stroking.
*/
private AffineTransform inverse;
/**
* Our base stroke.
*/
private Stroke stroke;
/**
* Creates a TransformedStroke based on another Stroke
* and an AffineTransform.
*/
public TransformedStroke(Stroke base, AffineTransform at)
throws NoninvertibleTransformException
{
this.transform = new AffineTransform(at);
this.inverse = transform.createInverse();
this.stroke = base;
}
/**
* Strokes the given Shape with this stroke, creating an outline.
*
* This outline is distorted by our AffineTransform relative to the
* outline which would be given by the base stroke, but only in terms
* of scaling (i.e. thickness of the lines), as translation and rotation
* are undone after the stroking.
*/
public Shape createStrokedShape(Shape s) {
Shape sTrans = transform.createTransformedShape(s);
Shape sTransStroked = stroke.createStrokedShape(sTrans);
Shape sStroked = inverse.createTransformedShape(sTransStroked);
return sStroked;
}
}
My paint-method using it then looks like this:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
int height = getHeight();
int width = getWidth();
g.scale(width/4.0, height/7.0);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
g.getTransform()));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
Then my window looks like this:
I'm quite content with this, but if someone has more ideas, feel free to answer nevertheless.
Attention: This g.getTransform() is returning the complete transformation of g relative to the device space, not only the transformation applied after the .create(). So, if someone did some scaling before giving the Graphics to my component, this would still draw with a 2-device-pixel width stroke, not 2 pixels of the grapics given to my method. If this would be a problem, use it like this:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
AffineTransform trans = new AffineTransform();
int height = getHeight();
int width = getWidth();
trans.scale(width/4.0, height/7.0);
g.transform(trans);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
trans));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
In Swing normally your Graphics given to the paintComponent is only translated (so (0,0) is the upper left corner of your component), not scaled, so there is no difference.
There is a simpler and less 'hacky' solution than the original TransformedStroke answer.
I got the idea when I read how the rendering pipeline works:
(from http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)
If the Shape is to be stroked, the Stroke attribute in the Graphics2D context is used to generate a new Shape that encompasses the stroked path.
The coordinates of the Shape’s path are transformed from user space into device space according to the transform attribute in the Graphics2D context.
The Shape’s path is clipped using the clip attribute in the Graphics2D context.
The remaining Shape, if any, is filled using the Paint and Composite attributes in the Graphics2D context.
What you, and I, ideally seek is a way to swap the first two steps.
If you look closely at the second step, TransformedStroke already contains part of the solution.
Shape sTrans = transform.createTransformedShape(s);
solution
In stead of:
g.scale(...), g.transform(...), whatever,
g.draw(new Rectangle( 1, 2, 2, 4));
Or, using TransformedStroke:
g.setStroke(new TransformedStroke(new BasicStroke(2f), g.getTransform());
g.draw(new Rectangle( 1, 2, 2, 4));
I propose you do:
transform =whatever,
g.draw(transform.createTransformedShape(new Rectangle( 1, 2, 2, 4));
Don't transform g anymore. Ever. Transform the shapes instead, using a transform that you make and modify yourself.
discussion
TransformedStroke feels more like a 'hack' than a way the authors of Stroke meant the interface to be used. It also requires an extra class.
This solution keeps a separate Transform around and modifies the Shape instead of transforming the Graphics object. This is however in no way a hack, because I'm not abusing existing functionality but using API functionality exactly how it's meant to be used. I'm just using the more explicit parts of the API instead of the 'shortcut'/'convenience' methods of the API (g.scale() etc.).
Performance-wise, this solution can only be more efficient. Effectively one step is now skipped. In the original solution, TransformedStroke transforms the shape twice and strokes the shape once. This solution transforms the shape explicitly and the *current* stroke strokes the shape once.
Have you just tried to make the int x and int y on the application bigger like int x = 500 int y = 900??? Also my suggestion is that with out rewritten the whole code is to implement where the recs are thicker when the app is closer together more like doubling the rectangle on the top and the bottom but when the app is extended the recs on the top and bottom go back to normal...
So I'm trying to figure out how to implement a method of selecting lines or edges in a drawing area but my math is a bit lacking. This is what I got so far:
A collection of lines, each line has two end points (one to start and one to end the line)
The lines are drawn correctly on a canvas
Mouse clicks events are received when clicking the canvas, so I can get the x and y coordinate of the mouse pointer
I know I can iterate through the list of lines, but I have no idea how to construct an algorithm to select a line by a given coordinate (i.e. the mouse click). Anyone got any ideas or point me to the right direction?
// import java.awt.Point
public Line selectLine(Point mousePoint) {
for (Line l : getLines()) {
Point start = l.getStart();
Point end = l.getEnd();
if (canSelect(start, end, mousePoint)) {
return l; // found line!
}
}
return null; // could not find line at mousePoint
}
public boolean canSelect(Point start, Point end, Point selectAt) {
// How do I do this?
return false;
}
Best way to do this is to use the intersects method of the line. Like another user mentioned, you need to have a buffer area around where they clicked. So create a rectangle centered around your mouse coordinate, then test that rectangle for intersection with your line. Here's some code that should work (don't have a compiler or anything, but should be easily modifiable)
// Width and height of rectangular region around mouse
// pointer to use for hit detection on lines
private static final int HIT_BOX_SIZE = 2;
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Line2D clickedLine = getClickedLine(x, y);
}
/**
* Returns the first line in the collection of lines that
* is close enough to where the user clicked, or null if
* no such line exists
*
*/
public Line2D getClickedLine(int x, int y) {
int boxX = x - HIT_BOX_SIZE / 2;
int boxY = y - HIT_BOX_SIZE / 2;
int width = HIT_BOX_SIZE;
int height = HIT_BOX_SIZE;
for (Line2D line : getLines()) {
if (line.intersects(boxX, boxY, width, height) {
return line;
}
}
return null;
}
Well, first off, since a mathematical line has no width it's going to be very difficult for a user to click exactly ON the line. As such, your best bet is to come up with some reasonable buffer (like 1 or 2 pixels or if your line graphically has a width use that) and calculate the distance from the point of the mouse click to the line. If the distance falls within your buffer then select the line. If you fall within that buffer for multiple lines, select the one that came closest.
Line maths here:
http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html
Shortest distance between a point and a line segment
If you use the 2D api then this is already taken care of.
You can use Line2D.Double class to represent the lines. The Line2D.Double class has a contains() method that tells you if a Point is onthe line or not.
Sorry, mathematics are still required... This is from java.awt.geom.Line2D:
public boolean contains(double x,
double y)
Tests if a specified coordinate is inside the boundary of this Line2D.
This method is required to implement
the Shape interface, but in the case
of Line2D objects it always returns
false since a line contains no area.
Specified by:
contains in interface Shape
Parameters:
x - the X coordinate of the specified point to be tested
y - the Y coordinate of the specified point to be tested
Returns:
false because a Line2D contains no area.
Since:
1.2
I recommend Tojis answer