Problem with collision detection: Shape, Area

Hi,

I am writing a game and would like to check whether there is a collision between my player and a given object:

// define barrier

public Shape drawBarrier(){

GeneralPath path =new GeneralPath();

path.moveTo(50, 50);

path.lineTo(90, 70);

path.lineTo(40, 150);

path.closePath();

barrierArea =new Area(path);

return path;

}

(...)

}

I am storing the coordinates of the player in an ArrayList:

// variables: store player moves

private List<Point2D> allPlayerCoordinates =new ArrayList<Point2D>();

private Point2D playerCurrentPosition;

(...)

// store player moves with every mouse click

publicvoid mouseClicked(MouseEvent evt){

if (SwingUtilities.isLeftMouseButton(evt)){

playerCurrentPosition =new Point(evt.getX(), evt.getY());

allPlayerCoordinates.add(playerCurrentPosition);

}

(...)

// create playerPath and draw it

protectedvoid paintComponent(Graphics g){

super.paintComponent(g);

Graphics2D g2 = (Graphics2D) g;

playerPath =new GeneralPath();

for (int i = 1 ; i < allPlayerCoordinates.size(); i++){

Point2D point = allPlayerCoordinates.get(i);

playerPath.lineTo(point.getX(), point.getY());

}

g2.draw(playerPath);

(...)

Now I am test whether there i a collision:

// check for collision

privateboolean isCollision(){

playerArea =new Area(playerPath);

playerArea.intersect(barrierArea);

if (!playerArea.isEmpty()){

returntrue;

}

elsereturnfalse;

}

However, my method isCollision returns true even if there is no collision and even if the space between the shape and the player coordinates is large enough: the following player moves result in a collision:

[Point2D.Double[300.0, 100.0], java.awt.Point[x=18,y=13], java.awt.Point[x=0,y=88]]

I know that the collision detection using the Area and Shape class is not pixel accurate... but I get a collision although the space between the player moves and the shape is large enough... the situation gets even more worse when I draw other shapes.

I hope you understand my problem and could help me - thanks a lot!

[3621 byte] By [SFLa] at [2007-11-27 4:43:36]
# 1

I suspect what is occuring is that the Area.intersects method closes the shape defined by those three points and uses that area to check if it

intersects the barrier, which it clearly does (i.e. draw the final line between the third point and the first point). You may want to try the following

brute-force approach instead:

private boolean isCollision() {

boolean rv = false;

int size = allPlayerCoordinates.size();

if ( size > 1 ) {

size--;

// In real use, probably just check the last line segment, don't need to recheck all every time

for ( int k = 0; !rv && k < size; k++ ) {

Line2D line = new Line2D.Double( allPlayerCoordinates.get( k ), allPlayerCoordinates.get( k+1 ) );

rv = intersects( line, barrierArea );

}

}

return rv;

}

public boolean intersects(Line2D line, Shape shape) {

boolean rv = shape.contains( line.getP1() ) || shape.contains( line.getP2() );

if ( ! rv ) {

Point2D middle = new Point2D.Double( (line.getX1() + line.getX2()) / 2, (line.getY1() + line.getY2()) / 2);

rv = shape.contains( middle );

if ( !rv ) {

if ( ! close( line.getP1(), middle ) )

rv = intersects( new Line2D.Double( line.getP1(), middle ), shape );

if ( ! rv && ! close( line.getP2(), middle ) )

rv = intersects( new Line2D.Double( middle, line.getP2() ), shape );

}

}

return rv;

}

private boolean close( Point2D one, Point2D two ) {

double dx = one.getX() - two.getX();

double dy = one.getY() - two.getY();

return ( 2 > dx && dx > -2 && 2 > dy && dy > -2 );

}

JayDSa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...
# 2
Thanks for your reply! I will try it out later :)I guess there is no way to automatically exclude the last line segment, say, the line segment between start and end point?
SFLa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...
# 3

I'm not sure what you mean by last, especially in light of the trailing

clause. I'm assuming you mean the line segment previous to the latest

one? As I put in the comment, I think all you really need to do is check the

latest line segment, since any previous line segments should presumably

have passed the collision test. This is just a matter of checking the line

Line2D line = new Line2D.Double(

allPlayerCoordinates.get( allPlayerCoordinates.size()-2 ),

allPlayerCoordinates.get( allPlayerCoordinates.size()-1 ) );

rather than checking all of them.

JayDSa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...
# 4

JayDS, thanks a lot for your help! I am going to try it out...

Edit: I guess I have a problem in finding out the correct way to do the collision detection... maybe a error in reasoning:

this is my code to do the collision detection:

private boolean isCollision() {

playerArea = new Area(playerPath);

playerArea.intersect(barrierArea);

if (!playerArea.isEmpty()) {

return true;

}

else return false;

}

playerArea consists of all lineTo's, including the closing line segment which caused the originally problem.

I declare this last line segment as follows:

if(allPlayerCoordinates.size() > 2) {

playerClosingPath.moveTo(allPlayerCoordinates.get(allPlayerCoordinates.size()-2).getX(), allPlayerCoordinates.get(allPlayerCoordinates.size()-2).getY());

playerClosingPath.lineTo(allPlayerCoordinates.get(allPlayerCoordinates.size()-1).getX(), allPlayerCoordinates.get(allPlayerCoordinates.size()-1).getY());

playerClosingArea = new Area(playerClosingPath);

}

When I detect a collision it can be caused by the playerArea - then I will check if the collision is caused by the last line segment... let us assume it is caused by the last line segment, which is irrelevant for me, I can return false, meaning no collision; however, there could be still a collision caused by another line segment.

I meet a problem if the last segment as well as the whole playerArea return true, meaning collision - I don't know where the collision appeared.

As far as I understand it I have to check each line segment for collision.

Am I misunderstanding something?

Thanks!

Message was edited by:

SFL

SFLa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...
# 5

At this point, it becomes harder for me to comment intelligibly because I'm

dealing with fragments of code. It sounds like what you're trying to do is

check if the collision is caused by the line segment which closes the area

encompassed by the playerPath (and which isn't actually part of the path).

Conceptually, I don't see anything wrong with that, but you may not need to do

so. I guess it depends on if the barriers will be moving as well, such that they

can collide with an older line segment in the path. If not, all you need to check

is the latest line segment.

I am also wondering if you are creating playerClosingPath each time, or

merely adding points to it via moveTo and lineTo; that could also be part

of the problem.

JayDSa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...
# 6

omg, I am always checking all line segments instead of only the latest one... well, I am still a newbie :)

JayDS, thanks a lot, you really helped me finishing my project! Thanks a million for that!

There is just one last problem: I have problems getting the latest line segment: this is the code how I draw the player moves:

if(allPlayerCoordinates.size() > 0) {

firstPoint = allPlayerCoordinates.get(0);

playerPath.moveTo(firstPoint.getX(), firstPoint.getY());

}

// storing the latest line segment here?

for (int i = 1 ; i < allPlayerCoordinates.size(); i++) {

Point2D point = allPlayerCoordinates.get(i);

playerPath.lineTo(point.getX(), point.getY());

}

g2.draw(playerPath);

Now I always have to store the last and the last but one coordinate. I can't work with, e.g.,

playerLatestLineSegmentPath.moveTo(allPlayerCoordinates.get(allPlayerCoordinates.size()-2).getX(), allPlayerCoordinates.get(allPlayerCoordinates.size()-2).getY());

playerLatestLineSegmentPath.lineTo(allPlayerCoordinates.get(allPlayerCoordinates.size()-1).getX(), allPlayerCoordinates.get(allPlayerCoordinates.size()-1).getY());

because then I get NullPointerExceptions.

I guess I have to pick these coordinates in my for-loop... but I can't figure it out how :/

SFLa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...
# 7

Since you're getting a NPE, the only choices are that playerLatestLineSegmentPath or allPlayerCoordinates is null.

Here is an example which uses Mouse1 to add points and Mouse3 to remove them. Perhaps you can compare and

contrast its logic with yours.

import java.awt.*;

import java.awt.event.MouseAdapter;

import java.awt.event.MouseEvent;

import java.awt.geom.*;

import javax.swing.*;

import java.util.*;

import java.util.List;

/*

* Application Driver class

*/

public class CollisionTest {

public static void main( String[] args ) {

JFrame app = new JFrame( "Collision Test" );

app.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

Container c = app.getContentPane();

AreaPanel apnl = new AreaPanel();

c.add( apnl, BorderLayout.CENTER );

app.setSize( 600, 400 );

app.setLocationRelativeTo( null );

app.setVisible( true );

}

static class AreaPanel extends JPanel {

private Area barrierArea;

private List<Point> allPlayerCoordinates = new ArrayList<Point>();

private GeneralPath playerPath;

public AreaPanel() {

drawBarrier();

playerPath = new GeneralPath();

addMouseListener( new MouseAdapter() {

public void mousePressed(MouseEvent e) {

if ( MouseEvent.BUTTON1 == e.getButton() ) {

Point p = e.getPoint();

if ( allPlayerCoordinates.size() == 0 ) {

playerPath.moveTo( p.x, p.y );

} else {

playerPath.lineTo( p.x, p.y );

}

allPlayerCoordinates.add( p );

} else if ( MouseEvent.BUTTON3 == e.getButton() ) {

int size = allPlayerCoordinates.size();

if ( size > 0 ) {

size--;

allPlayerCoordinates.remove( size );

playerPath = new GeneralPath();

for ( int k = 0; k < size; k++ ) {

Point p = allPlayerCoordinates.get( k );

if ( k == 0 ) {

playerPath.moveTo( p.x, p.y );

} else {

playerPath.lineTo( p.x, p.y );

}

}

}

}

repaint();

}

} );

}

public void paintComponent( Graphics g ) {

// Paint panel

super.paintComponent( g );

Graphics2D g2 = (Graphics2D) g;

g2.setColor( Color.RED );

g2.fill( barrierArea );

g2.setColor( Color.BLUE );

g2.draw( playerPath );

System.out.println( isCollision() );

}

public Shape drawBarrier() {

GeneralPath path = new GeneralPath();

path.moveTo( 50, 50 );

path.lineTo( 90, 70 );

path.lineTo( 40, 150 );

path.closePath();

barrierArea = new Area( path );

return path;

}

private boolean isCollision() {

boolean rv = false;

int size = allPlayerCoordinates.size();

if ( size > 1 ) {

Line2D line = new Line2D.Double( allPlayerCoordinates.get( size-2 ), allPlayerCoordinates.get( size-1 ) );

rv = intersects( line, barrierArea );

}

return rv;

}

public boolean intersects( Line2D line, Shape shape ) {

boolean rv = shape.contains( line.getP1() ) || shape.contains( line.getP2() );

if ( !rv ) {

Point2D middle = new Point2D.Double( ( line.getX1() + line.getX2() ) / 2,

( line.getY1() + line.getY2() ) / 2 );

rv = shape.contains( middle );

if ( !rv ) {

if ( !close( line.getP1(), middle ) )

rv = intersects( new Line2D.Double( line.getP1(), middle ), shape );

if ( !rv && !close( line.getP2(), middle ) )

rv = intersects( new Line2D.Double( middle, line.getP2() ), shape );

}

}

return rv;

}

private boolean close( Point2D one, Point2D two ) {

double dx = one.getX() - two.getX();

double dy = one.getY() - two.getY();

return ( 2 > dx && dx > -2 && 2 > dy && dy > -2 );

}

}

}

JayDSa at 2007-7-12 9:55:24 > top of Java-index,Desktop,Core GUI APIs...