2D collision detection

Hello!

I am looking for a way to detect collision (overlap) between any pair of 2D polygon.

I will be using .png’s to create polygons/shapes from images using a rotary algorithm.

I already know how to convert the image to a list of points, how do I check the polygons for overlaps?

Does java standard lib already have ways of doing this?

Thanks in advance to anyone who knows anything.

Why not just give your entities a radius and check in the update loop if two are too close? you even could make segments so you can operate on a sub set. Or just use 3D elements put look top down. There are projections which helps you make it look 2D. then you could use regular collision detection.

Java AWT has come classes that will do this. Polygon only deals with integer coordinates as I recall but I have done this before… maybe with Area? I don’t remember. And anyway it will only tell you true or false.

If you ever think you will need more than that then I highly recommend Dyn4j. It’s a nice 2D physics engine but it also let’s you create arbitrary 2D collision shapes and check intersection and stuff.

1 Like

2d for performance on tablet, shape will be complex so no circle collisions. Thanks anyways

Thanks Paul I will try it out. BTW, I am using Lemur nodes for my 2d objects, works great :smile:

P.S. I noticed that the button commands get triggered twice on when “clicked” on Android, I get around it by only using the event every second time and it seems stable. Just thought you should know.

1 Like

Cool. I do that all the time also. I keep telling myself I will implement something better whenever performance becomes an issue but it hasn’t so far.

1 Like

Hmmm… that is weird. Not sure why that would happen. I didn’t write the code but if you feel a wild urge then maybe you can look into it someday. It may be something related to multitouch support. The actual event processing is handled the same for both touch and mouse but how those events are handed to the PickEventSession is different… and the touch version keeps multiple PickEventSessions for multitouch. Maybe a second touch gets triggered somehow.

Yeah, I think it needs to check the environment to see what platform it’s working on and then use a RawInputListener that listens for “TAP” event. I can’t even find where your mouse listener is nested :grimacing:

GuiGlobals adds a different app state depending on the platform. So on Android it adds the TouchEventState (or something like that) in the com.simsilica.lemur.event package.

That state adds a RawInputListener and does some stuff with that to pass events on to a PickEventSession. Maybe you are getting a down+up and a tap or something.

Ok, i will check it out

I found the segment, I can’t branch it off because I am using 3.1.0-beta1 and don’t really feel like installing 3.0 stable. That is the only way I can recompile the .jar correct?

In the code below, I don’t know what you are using the UP and DOWN events for (e.g. for sliders later on?). But in the mean time you can add a “case TAP:” and that should avoid the whole double event thing.

Copying whats in the “UP” to the new “TAP” would probably work.

I can create a branch and commit but I will need a fresh compiled .jar to test it out I think, still new to github.

Tell me how I can help :triumph:

protected class TouchObserver extends DefaultRawInputListener {

    @Override
    public void onTouchEvent(TouchEvent te) {
        if (!isEnabled()) {
            return;
        }
        TouchAppState.PointerData pointerData;
        switch (te.getType()) {
            case TAP:
                pointerData = pointerDataMap.get(te.getPointerId());
                if (pointerData != null) {
                    pointerData.lastX = (int)te.getX();
                    pointerData.lastY = (int)te.getY();
                    if (dispatchButton(pointerData, false)) {
                        te.setConsumed();
                    }
                    pointerDataMap.remove(te.getPointerId());
               }
            break;
                break;
            case DOWN:
                pointerData = getPointerData(
                        te.getPointerId(), (int)te.getX(), (int)te.getY());
                if (dispatchButton(pointerData, true)) {
                    te.setConsumed();
                }
                break;
            case MOVE:
                pointerData = pointerDataMap.get(te.getPointerId());
                if (pointerData != null) {
                    pointerData.lastX = (int)te.getX();
                    pointerData.lastY = (int)te.getY();
                }
                break;
            case UP:
                pointerData = pointerDataMap.get(te.getPointerId());
                if (pointerData != null) {
                    pointerData.lastX = (int)te.getX();
                    pointerData.lastY = (int)te.getY();
                    if (dispatchButton(pointerData, false)) {
                        te.setConsumed();
                    }
                    pointerDataMap.remove(te.getPointerId());
                }
                break;
            default:
                break;
        }
    }
}

Latest lemur should compile with beta1 fine.

Got the collisions working!

Here is the code:

This section creates a polygon from any image by starting at the edge of the image and working towards the center until a non transparent pixel is detected. Action is repeated for 360 degrees. This way, we get a more or less accurate outline.

*Not accurate for extremely concave shapes as the algorithm only takes the out-most non-transparent pixel for each degree step.

(step is in rads)

public static Polygon GetPolygonFromTexture(Texture t, float step, float scale){
    
    Polygon poly = new Polygon();
    
    Image img = t.getImage();
    int max = img.getWidth()/2;
    if(img.getHeight()/2 > img.getWidth()) max = img.getHeight()/2;
    ImageRaster rast = ImageRaster.create(img);        
    max *= 1.4143f;
    
    ColorRGBA trans = new ColorRGBA(1,1,1,0);
    
    float angle;
    Vector2f vec = new Vector2f(0, max);
    for (angle = 0; angle <= FastMath.TWO_PI; angle += step){
        System.out.println("Angle: "+FastMath.RAD_TO_DEG*angle);
                
        Vector2f unit = vec.negate().divide(max);
        Vector2f test = unit.clone();
        Vector2f pos = vec.clone();
        Vector2f imgPoint;
        ColorRGBA color;
        for (int i = 0; i < max ; i++){
            pos = vec.add(test);
            if(pos.x > img.getWidth()/2 || (pos.y > img.getHeight()/2)){
                test = test.add(unit);
                continue;
            }
            try{
                imgPoint = new Vector2f(pos.x+(img.getWidth()/2), (img.getHeight()/2)-pos.y);
                color = rast.getPixel((int)imgPoint.x, (int)imgPoint.y);
                if((color.a != trans.a) && (color.b != trans.b) && (color.g != trans.g) && (color.r != trans.r)){
                    poly.addPoint((int)(-pos.x*scale), (int)(-pos.y*scale));                        
                    System.out.println("Point: "+pos.toString());
                    break;
                }
            }catch(IllegalArgumentException e){
                //pixel is outside of the image, continue.
            }
            test = test.add(unit);
        }
        vec.rotateAroundOrigin(step, true);   
    }
    
    return poly;
}

Create a node for each point and add it to the main entity node (so that the points stay aligned when the main node is rotated)

And than, for each collison, create a new poly from all the nodes and cast to Area:

private ArrayList<Container> points = new ArrayList<Container>();

@Override    
public Area getArea(){
    Polygon poly = new Polygon();        
    for(Node n : points){
        poly.addPoint((int)n.getWorldTranslation().x, (int)n.getWorldTranslation().y);
    }                
    return new Area(poly);
}

public boolean collideWith(Entity e){        
    Area a = getArea();        
    a.intersect(e.getArea());
    return !a.isEmpty();
}

The initial polygon is only created at initialisaion. What do you think?

P.S. I will get around to correcting the double event thing soon, just need to find the latest lemur.

It’s on github.

Edit: I used JDK 1.8 instead of 1.7, could that cause errors?

… I got the Lemur master inside JME3 and did the modification but when I use the freshly compiled .jar I get a pre-dexing error when building to android. All I get is this:

Pre-Dexing C:\Users\Ghost\Documents\GitHub\SpaceRocks\mobile\libs\Lemur-1.8.1.jar → Lemur-1.8.1- 7526c920202aef68050a21681a09916d.jar

UNEXPECTED TOP-LEVEL EXCEPTION:
java.lang.RuntimeException: Exception parsing classes
at com.android.dx.command.dexer.Main.processClass(Main.java:752)
at com.android.dx.command.dexer.Main.processFileBytes(Main.java:718)
at com.android.dx.command.dexer.Main.access$1200(Main.java:85)
at com.android.dx.command.dexer.Main$FileBytesConsumer.processFileBytes(Main.java:1645)
at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284)
at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166)
at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144)
at com.android.dx.command.dexer.Main.processOne(Main.java:672)
at com.android.dx.command.dexer.Main.processAllFiles(Main.java:574)
at com.android.dx.command.dexer.Main.runMonoDex(Main.java:311)
at com.android.dx.command.dexer.Main.run(Main.java:277)
at com.android.dx.command.dexer.Main.main(Main.java:245)
at com.android.dx.command.Main.main(Main.java:106)
Caused by: com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000)
at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472)
at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406)
at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388)
at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251)
at com.android.dx.command.dexer.Main.parseClass(Main.java:764)
at com.android.dx.command.dexer.Main.access$1500(Main.java:85)
at com.android.dx.command.dexer.Main$ClassParserTask.call(Main.java:1684)
at com.android.dx.command.dexer.Main.processClass(Main.java:749)
… 12 more
1 error; aborting
C:\Users\Ghost\Documents\GitHub\SpaceRocks\nbproject\mobile-impl.xml:10: The following error occurred while executing this line:
C:\AndroidSDK\tools\ant\build.xml:888: The following error occurred while executing this line:
C:\AndroidSDK\tools\ant\build.xml:890: The following error occurred while executing this line:
C:\AndroidSDK\tools\ant\build.xml:902: The following error occurred while executing this line:
C:\AndroidSDK\tools\ant\build.xml:283: null returned: 1

Any ideas?

Edit: I get this when building with JDK 1.7:

ant -f C:\Users\Ghost\Documents\GitHub\Lemur -Dnb.internal.action.name=clean clean
C:\Users\Ghost\Documents\GitHub\Lemur\nbproject\build-impl.xml:88: The J2SE Platform is not correctly set up.
Your active platform is: JDK_1.7, but the corresponding property “platforms.JDK_1.7.home” is not found in the project’s properties files.
Either open the project in the IDE and setup the Platform with the same name or add it manually.
For example like this:
ant -Duser.properties.file=<path_to_property_file> jar (where you put the property “platforms.JDK_1.7.home” in a .properties file)
or ant -Dplatforms.JDK_1.7.home=<path_to_JDK_home> jar (where no properties file is used)
BUILD FAILED (total time: 0 seconds)

Dunno… did you compile the source with 1.8?

…because that part worries me. Lemur is built using gradle. So “master inside JME3” could mean a few different things and some of them are scary.

Erm, maybe the question I should ask is:

How exactly do I go about making a change to lemur, getting a new .jar, and testing that jar in my app?

In the Lemur directory run:
gradlew build
(or if you have gradle installed and its a compatible version then just: gradle build)

The first time you run gradlew it will download gradle.

Anyway, after that all finishes there will be jars in the lemur/build/lib directory.

How do I run gradlew in the Lemur directory? (I use windows…)

? Do you really not know how to open a command line in windows or am I misunderstanding the question?