BoundingBox.collideWithRay() fails when box is too slim, floating point precission fail? jme3 r7349

EDIT: ok, I guess it is the low floating point precision, any fixes for this example though?

This is based on this thread/post.

use the following code to test

Note that I also edited this, to actually see more clearly:

[patch]Index: src/core/com/jme3/bounding/BoundingBox.java

===================================================================

— src/core/com/jme3/bounding/BoundingBox.java (revision 7353)

+++ src/core/com/jme3/bounding/BoundingBox.java (working copy)

@@ -707,6 +707,9 @@

  • @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)

    */

    private int collideWithRay(Ray ray, CollisionResults results) {
  • System.out.println(ray);
  • System.out.println("sizeX=" + this.getXExtent() + "sizeY="
  •   + this.getYExtent() + &quot;sizeZ=&quot; + this.getZExtent());<br />
    

TempVars vars = TempVars.get();

assert vars.lock();

Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center);

@@ -735,22 +738,27 @@

results.addCollision(result);

result = new CollisionResult(points[1], distances[1]);

results.addCollision(result);

  •            System.out.println(&quot;2 collisions&quot;);<br />
    

return 2;

}

Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin);

CollisionResult result = new CollisionResult(point, t[0]);

results.addCollision(result);

  •        System.out.println(&quot;1 collisions&quot;);<br />
    

return 1;

}

  •    System.out.println(&quot;no collision&quot;);<br />
    

return 0;

}

[/patch]

When you run code, there are no collisions, just press W or S to move camera forward/backward to trigger 2 collisions and if you get close enough you can see when the closest collision is actually on the backface of the box (though most of the time it’s on the front face) or just comment and uncomment the blocks that set the camera pos&rot, marked with “XXX:” in the code

backside collision:

http://i.imgur.com/g4hf4.png

====

no collisions:

http://i.imgur.com/beqwu.png

[java]package org.jme3.forum2;

import java.util.prefs.BackingStoreException;

import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResult;

import com.jme3.collision.CollisionResults;

import com.jme3.font.BitmapText;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Quaternion;

import com.jme3.math.Ray;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.shape.Box;

import com.jme3.system.AppSettings;

/**

*

/

public class CollisionFail extends SimpleApplication {

public static void main(String[] args) throws BackingStoreException {

CollisionFail app = new CollisionFail();

AppSettings aps = new AppSettings(true);

aps.load(aps.getTitle());

aps.setVSync(true);

app.setShowSettings(false);

app.setSettings(aps);

app.start();

}

private Node shootables;

private Geometry mark;

/

  • (non-Javadoc)

    *
  • @see com.jme3.app.SimpleApplication#simpleInitApp()

    /

    @Override

    public void simpleInitApp() {

    Box boxMesh = new Box(0.5f, 0.9f, 0.000001f);

    Geometry boxGeo = new Geometry("box", boxMesh);

    Material boxMat = new Material(assetManager,

    "Common/MatDefs/Misc/WireColor.j3md");

    boxMat.setColor("Color", ColorRGBA.Green);

    boxGeo.setMaterial(boxMat);

    shootables = new Node();

    shootables.attachChild(boxGeo);

    rootNode.attachChild(shootables);

    initCrossHairs();

    initMark();

    flyCam.setMoveSpeed(10f);

    // XXX: closest collision is on the backside:

    cam.setLocation(new Vector3f(-3.135194f, 0.008213693f, 11.205608f));

    cam.setRotation(new Quaternion(4.205436E-4f, 0.9896395f,

    -0.0028993362f, 0.14354452f));

    // XXX: there are no collisions:

    cam.setLocation(new Vector3f(-36.74216f, 0.70129883f, 124.61681f));

    cam.setRotation(new Quaternion(4.205436E-4f, 0.9896395f,

    -0.0028993362f, 0.14354452f));

    }

    private final Vector3f marksScale = new Vector3f(0.5f, 0.5f, 1f);

    protected void initMark() {

    Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));

    arrow.setLineWidth(3);

    mark = new Geometry("markGeo", arrow);

    Material mark_mat = new Material(assetManager,

    "Common/MatDefs/Misc/SolidColor.j3md");

    mark_mat.setColor("Color", ColorRGBA.Red);

    mark.setMaterial(mark_mat);

    }

    /
    * A centred plus sign to help the player aim. */

    protected void initCrossHairs() {

    // guiNode.detachAllChildren();

    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

    BitmapText ch = new BitmapText(guiFont, false);

    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);

    ch.setText("+"); // crosshairs

    ch.setLocalTranslation(

    // center

    settings.getWidth() / 2
  • guiFont.getCharSet().getRenderedSize() / 3 * 2,

    settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);

    guiNode.attachChild(ch);

    }

    /*
  • (non-Javadoc)

    *
  • @see com.jme3.app.SimpleApplication#simpleUpdate(float)

    */

    @Override

    public void simpleUpdate(float tpf) {

    CollisionResults results = new CollisionResults();

    Ray ray = new Ray(cam.getLocation(), cam.getDirection());

    // 3. Collect intersections between Ray and Shootables in

    // results list.

    shootables.collideWith(ray, results);

    if (results.size() > 0) {

    CollisionResult closest = results.getClosestCollision();

    Vector3f worldContact = closest.getContactPoint();

    Vector3f normal = closest.getContactNormal();

    worldContact = rootNode.worldToLocal(worldContact, null);

    rootNode.attachChild(mark);

    mark.setLocalTranslation(worldContact);

    Vector3f upVec = rootNode.worldToLocal(Vector3f.UNIT_Y, null);

    Quaternion q = new Quaternion();

    // makes Z axis be in the same direction as normal

    // (our mark arrow is on the Z axis)

    q.lookAt(normal, upVec.normalize());

    q = mark.getParent().getWorldRotation().inverse().mult(q);

    mark.setLocalRotation(q);

    // if I want to keep mark’s scale to 1, regardless of parents’

    // transforms:

    mark.setLocalScale(marksScale.divide(mark.getParent()

    .getWorldScale()));

    } else {

    rootNode.detachChild(mark);

    }

    }

    }

    [/java]

    EDIT: why is this relevant? because BoundingBox is the bound of ie. a Quad and the intersection of it with Ray will fail quite a lot

Perhaps you can try BoundingBox.intersects(Ray)? It uses a slightly different algorithm.

Also, can you please check to make sure the extents of the bounding box are not zero?

1 Like

Thank you for your reply!

in order to see the extents (of the boundingbox) are not zero (just very close to zero though)

I applied only this patch (to the original BoundingBox.java file from jme3 r7357):

[patch]Index: src/core/com/jme3/bounding/BoundingBox.java

===================================================================

— src/core/com/jme3/bounding/BoundingBox.java (revision 7357)

+++ src/core/com/jme3/bounding/BoundingBox.java (working copy)

@@ -707,6 +707,8 @@

  • @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)

    */

    private int collideWithRay(Ray ray, CollisionResults results) {
  • System.out.println("x="+this.getXExtent()+" / y="+this.getYExtent()+" / z="+this.getZExtent());
  • System.out.format(“x=%10.30f / y=%10.30f / z=%10.30f n”, this.getXExtent(),this.getYExtent(),this.getZExtent());

    TempVars vars = TempVars.get();

    assert vars.lock();

    Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center);

    [/patch]

    what’s I’m getting on console is this:
x=0.5 / y=0.9 / z=1.0E-6
x=0.500000000000000000000000000000 / y=0.899999976158142100000000000000 / z=0.000000999999997475242700000000

the Z one is rather small, I believe the smaller it is the most likely it is for the collision to fail, I mean it will fail more often (depending on the Ray angle of incidence(?))
after all the box was defined as:
[java]Box boxMesh = new Box(0.5f, 0.9f, 0.000001f);[/java]
=============
now on to using BoundingBox.intersects(Ray) ...
it works 100% (of what I tested anyway)
after applying this patch to BoundingBox, I noticed it always intersects when collideWithRay returns >0 collisions, and when it does return 0 collisions(but when it should've returned 1 or 2), the intersection is still detected - so yeah intersects works, too bad it's only boolean :) I want the coords xD
[patch]Index: src/core/com/jme3/bounding/BoundingBox.java
===================================================================
--- src/core/com/jme3/bounding/BoundingBox.java (revision 7357)
+++ src/core/com/jme3/bounding/BoundingBox.java (working copy)
@@ -707,6 +707,8 @@
* @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)
*/
private int collideWithRay(Ray ray, CollisionResults results) {
+ System.out.println("x="+this.getXExtent()+" / y="+this.getYExtent()+" / z="+this.getZExtent());
+ System.out.format("x=%10.30f / y=%10.30f / z=%10.30f n", this.getXExtent(),this.getYExtent(),this.getZExtent());
TempVars vars = TempVars.get();
assert vars.lock();
Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center);
@@ -735,14 +737,21 @@
results.addCollision(result);
result = new CollisionResult(points[1], distances[1]);
results.addCollision(result);
+ if (!intersects(ray)) {
+ throw null;
+ }
return 2;
}
Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin);
CollisionResult result = new CollisionResult(point, t[0]);
results.addCollision(result);
+ if (!intersects(ray)) {
+ throw null;
+ }
return 1;
}
+ System.out.println("collideWithRay detects 0, and .intersects() detects:"+this.intersects(ray));
return 0;
}
[/patch]
I also noticed that if I move the camera behind the box (not inside it) it never detects the collision on the other face, while it previously detected it sometimes on the backface - now I am aiming at the backface only and it never detects it on the frontface (which is the new backface from this camera position) - this didn't make much sense did it? xD
Anyway I'll post the program as it is now (can't remember making any changes):
[java]package org.jme3.forum2;
import java.util.prefs.BackingStoreException;
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
/**
*
*/
public class CollisionFail extends SimpleApplication {
public static void main(String[] args) throws BackingStoreException {
CollisionFail app = new CollisionFail();
AppSettings aps = new AppSettings(true);
aps.load(aps.getTitle());
aps.setVSync(true);
app.setShowSettings(false);
app.setSettings(aps);
app.start();
}
private Node shootables;
private Geometry mark;
/*
* (non-Javadoc)
*
* @see com.jme3.app.SimpleApplication#simpleInitApp()
*/
@Override
public void simpleInitApp() {
Box boxMesh = new Box(0.5f, 0.9f, 0.000001f);
Geometry boxGeo = new Geometry("box", boxMesh);
Material boxMat = new Material(assetManager,
"Common/MatDefs/Misc/WireColor.j3md");
boxMat.setColor("Color", ColorRGBA.Green);
boxGeo.setMaterial(boxMat);
shootables = new Node();
shootables.attachChild(boxGeo);
rootNode.attachChild(shootables);
initCrossHairs();
initMark();
flyCam.setMoveSpeed(10f);
// XXX: closest collision is on the backside:
// cam.setLocation(new Vector3f(-3.135194f, 0.008213693f, 11.205608f));
// cam.setRotation(new Quaternion(4.205436E-4f, 0.9896395f,
// -0.0028993362f, 0.14354452f));
// XXX: there are no collisions:
cam.setLocation(new Vector3f(-36.74216f, 0.70129883f, 124.61681f));
cam.setRotation(new Quaternion(4.205436E-4f, 0.9896395f,
-0.0028993362f, 0.14354452f));
}
private final Vector3f marksScale = new Vector3f(0.5f, 0.5f, 1f);
protected void initMark() {
Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));
arrow.setLineWidth(3);
mark = new Geometry("markGeo", arrow);
Material mark_mat = new Material(assetManager,
"Common/MatDefs/Misc/SolidColor.j3md");
mark_mat.setColor("Color", ColorRGBA.Red);
mark.setMaterial(mark_mat);
}
/** A centered plus sign to help the player aim. */
protected void initCrossHairs() {
// guiNode.detachAllChildren();
guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
ch.setText("+"); // crosshairs
ch.setLocalTranslation(
// center
settings.getWidth() / 2
- guiFont.getCharSet().getRenderedSize() / 3 * 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
/*
* (non-Javadoc)
*
* @see com.jme3.app.SimpleApplication#simpleUpdate(float)
*/
@Override
public void simpleUpdate(float tpf) {
CollisionResults results = new CollisionResults();
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
// 3. Collect intersections between Ray and Shootables in
// results list.
shootables.collideWith(ray, results);
System.out.println("Collisions:" + results.size());
if (results.size() > 0) {
CollisionResult closest = results.getClosestCollision();
Vector3f worldContact = closest.getContactPoint();
Vector3f normal = closest.getContactNormal();
worldContact = rootNode.worldToLocal(worldContact, null);
rootNode.attachChild(mark);
mark.setLocalTranslation(worldContact);
Vector3f upVec = rootNode.worldToLocal(Vector3f.UNIT_Y, null);
Quaternion q = new Quaternion();
// makes Z axis be in the same direction as `normal`
// (our `mark` arrow is on the Z axis)
q.lookAt(normal, upVec.normalize());
q = mark.getParent().getWorldRotation().inverse().mult(q);
mark.setLocalRotation(q);
// if I want to keep mark's scale to 1, regardless of parents'
// transforms:
mark.setLocalScale(marksScale.divide(mark.getParent()
.getWorldScale()));
} else {
rootNode.detachChild(mark);
}
}
}
[/java]

ok this temporary ugly hack makes collideWithRay never miss a detection, however this doesn’t solve the returned contact point is on the backside of the box instead of the front side where it was in fact detected

[patch]Index: src/core/com/jme3/bounding/BoundingBox.java

===================================================================

— src/core/com/jme3/bounding/BoundingBox.java (revision 7357)

+++ src/core/com/jme3/bounding/BoundingBox.java (working copy)

@@ -723,7 +723,7 @@

&& clip(-direction.z, +diff.z - zExtent, t);

assert vars.unlock();


  •    if (notEntirelyClipped &amp;&amp; (t[0] != saveT0 || t[1] != saveT1)) {<br />
    
  •    if (((notEntirelyClipped)||(intersects(ray))) &amp;&amp; (t[0] != saveT0 || t[1] != saveT1)) {<br />
    

if (t[1] > t[0]) {

float[] distances = t;

Vector3f[] points = new Vector3f[] {

[/patch]



to get my meaning about the backside, just run this program and you see the normal is on the backside, and just move the mouse to see it jump from front to back side of the box

this is probably due to that method com.jme3.bounding.BoundingBox.clip(float, float, float[])



backside normal:

http://i.imgur.com/jllpT.png

======

frontside normal:

http://i.imgur.com/A2rXN.png

======

[java]package org.jme3.forum2;



import java.util.prefs.BackingStoreException;



import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResult;

import com.jme3.collision.CollisionResults;

import com.jme3.font.BitmapText;

import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Quaternion;

import com.jme3.math.Ray;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.shape.Box;

import com.jme3.system.AppSettings;



/**

*

/

public class CollisionFail extends SimpleApplication {



public static void main(String[] args) throws BackingStoreException {

CollisionFail app = new CollisionFail();

AppSettings aps = new AppSettings(true);

aps.load(aps.getTitle());

aps.setVSync(true);

app.setShowSettings(false);

app.setSettings(aps);



app.start();

}



private Node shootables;

private Geometry mark;



/

  • (non-Javadoc)

    *
  • @see com.jme3.app.SimpleApplication#simpleInitApp()

    /

    @Override

    public void simpleInitApp() {

    Box boxMesh = new Box(0.5f, 0.9f, 0.000001f);

    Geometry boxGeo = new Geometry("box", boxMesh);

    Material boxMat = new Material(assetManager,

    "Common/MatDefs/Misc/WireColor.j3md");

    boxMat.setColor("Color", ColorRGBA.Green);

    boxGeo.setMaterial(boxMat);

    shootables = new Node();

    shootables.attachChild(boxGeo);

    rootNode.attachChild(shootables);



    initCrossHairs();

    initMark();

    flyCam.setMoveSpeed(10f);



    // XXX: closest collision is on the backside:

    cam.setLocation(new Vector3f(-3.135194f, 0.008213693f, 11.205608f));

    cam.setRotation(new Quaternion(4.205436E-4f, 0.9896395f,

    -0.0028993362f, 0.14354452f));



    // XXX: there are no collisions:

    // cam.setLocation(new Vector3f(-36.74216f, 0.70129883f, 124.61681f));

    // cam.setRotation(new Quaternion(4.205436E-4f, 0.9896395f,

    // -0.0028993362f, 0.14354452f));

    }



    private final Vector3f marksScale = new Vector3f(0.5f, 0.5f, 1f);



    protected void initMark() {

    Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f));

    arrow.setLineWidth(3);

    mark = new Geometry("markGeo", arrow);

    Material mark_mat = new Material(assetManager,

    "Common/MatDefs/Misc/SolidColor.j3md");

    mark_mat.setColor("Color", ColorRGBA.Red);

    mark.setMaterial(mark_mat);

    }



    /
    * A centered plus sign to help the player aim. */

    protected void initCrossHairs() {

    // guiNode.detachAllChildren();

    guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");

    BitmapText ch = new BitmapText(guiFont, false);

    ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);

    ch.setText("+"); // crosshairs

    ch.setLocalTranslation(

    // center

    settings.getWidth() / 2
  • guiFont.getCharSet().getRenderedSize() / 3 * 2,

    settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);

    guiNode.attachChild(ch);

    }



    /*
  • (non-Javadoc)

    *
  • @see com.jme3.app.SimpleApplication#simpleUpdate(float)

    */

    @Override

    public void simpleUpdate(float tpf) {

    CollisionResults results = new CollisionResults();

    Ray ray = new Ray(cam.getLocation(), cam.getDirection());

    // 3. Collect intersections between Ray and Shootables in

    // results list.

    shootables.collideWith(ray, results);

    System.out.println(“Collisions:” + results.size());

    if (results.size() > 0) {

    CollisionResult closest = results.getClosestCollision();

    Vector3f worldContact = closest.getContactPoint();

    Vector3f normal = closest.getContactNormal();

    worldContact = rootNode.worldToLocal(worldContact, null);

    rootNode.attachChild(mark);

    mark.setLocalTranslation(worldContact);

    Vector3f upVec = rootNode.worldToLocal(Vector3f.UNIT_Y, null);

    Quaternion q = new Quaternion();

    // makes Z axis be in the same direction as normal

    // (our mark arrow is on the Z axis)

    q.lookAt(normal, upVec.normalize());

    q = mark.getParent().getWorldRotation().inverse().mult(q);

    mark.setLocalRotation(q);

    // if I want to keep mark’s scale to 1, regardless of parents’

    // transforms:

    mark.setLocalScale(marksScale.divide(mark.getParent()

    .getWorldScale()));

    } else {

    rootNode.detachChild(mark);

    }

    }

    }

    [/java]

I don’t really know what to do … We need a new algorithm it seems.

1 Like