[SOLVEd]worldToLocal not working right? with the jme3 r7291 fix

I mean this fix:

https://code.google.com/p/jmonkeyengine/source/detail?r=7291

which is good, but maybe there’s somewhere else that also needs changing? like worldToLocal ?

here’s the simple testcase:

the red sphere is on the point of collision between the Ray and the yellow Box, (Ray casted from middle of screen)

without the fix:

http://i.imgur.com/vT7pg.jpg

=====

with the r7291 fix:

http://i.imgur.com/RQMUe.jpg

====

[java]

package org.jme3.forum;

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.FastMath;

import com.jme3.math.Quaternion;

import com.jme3.math.Ray;

import com.jme3.math.Vector2f;

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Sphere;

import com.jme3.system.AppSettings;

/**

*

/

public class WorldToLocalTestCase extends SimpleApplication {

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

WorldToLocalTestCase app = new WorldToLocalTestCase();

AppSettings cfg = new AppSettings(true);

cfg.load(cfg.getTitle());

cfg.setVSync(true);

app.setSettings(cfg);

app.setShowSettings(false);

app.start();

}

private Node collidables;

private Node sphereNode;

private Vector2f middleOfScreen;

private Geometry sphereGeom;

/

  • (non-Javadoc)

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

    */

    @Override

    public void simpleInitApp() {

    flyCam.setMoveSpeed(1800f);

    cam.setFrustumFar(111111f);

    collidables = new Node(“collidables”);

    rootNode.attachChild(collidables);

    Vector3f boxHalfSize = new Vector3f(100, 100, 500);

    Box boxMesh = new Box(boxHalfSize, boxHalfSize.getX(),

    boxHalfSize.getY(), boxHalfSize.getZ());

    Geometry boxGeom = new Geometry(“Box”, boxMesh);

    Material boxMat = new Material(assetManager,

    “Common/MatDefs/Misc/WireColor.j3md”);

    boxMat.setColor(“m_Color”, ColorRGBA.Yellow);

    boxGeom.setMaterial(boxMat);

    collidables.attachChild(boxGeom);

    sphereNode = new Node(“sphereNode”);

    Sphere sphereMesh = new Sphere(10, 10, 20);

    sphereGeom = new Geometry(“contact sphere”, sphereMesh);

    Material sphereMat = new Material(assetManager,

    “Common/MatDefs/Misc/SolidColor.j3md”);

    sphereMat.setColor(“m_Color”, ColorRGBA.Red);

    sphereGeom.setMaterial(sphereMat);

    sphereNode.attachChild(sphereGeom);

    Node sphereParent = new Node(“markParent”);

    sphereParent.attachChild(sphereNode);

    rootNode.attachChild(sphereParent);

    sphereParent.setLocalTranslation(900f, 1200f, -300f);

    sphereParent.setLocalScale(8f, 1.9f, 3f);

    // markParent.setLocalScale(

    // 1f,

    // 1f,

    // 2f );

    sphereParent.setLocalRotation(new Quaternion().fromAngles(

    FastMath.DEG_TO_RAD * 159, FastMath.DEG_TO_RAD * 18,

    FastMath.DEG_TO_RAD * 310));

    collidables.setLocalTranslation(1000, 2000, -4000);

    collidables.setLocalRotation(new Quaternion().fromAngles(

    FastMath.DEG_TO_RAD * 100, FastMath.DEG_TO_RAD * 23,

    FastMath.DEG_TO_RAD * 230));

    collidables.setLocalScale(4.8f, 2.8f, 0.3f);

    // calculating this only once, assuming can’t change resolution once

    // started(if can, let me know how?)

    middleOfScreen = new Vector2f(cam.getWidth() / 2, cam.getHeight() / 2);

    // a “+” in middle of screen from where the Ray is cast

    initCrossHairs();

    // camera looks at a corner of yellow box

    cam.lookAt(boxGeom.localToWorld(Vector3f.ZERO, null), Vector3f.UNIT_Y);

    }

    private void initCrossHairs() {

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

    BitmapText ch = new BitmapText(guiFont, false);

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

    ch.setText("+"); // fake crosshairs :slight_smile:

    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) {

    Vector3f origin = cam.getWorldCoordinates(middleOfScreen, 0.0f);

    Vector3f direction = cam.getWorldCoordinates(middleOfScreen, 0.3f)

    .subtractLocal(origin).normalizeLocal();

    Ray ray = new Ray(origin, direction);

    CollisionResults results = new CollisionResults();

    collidables.collideWith(ray, results);

    if (results.size() > 0) {

    CollisionResult closestCollision = results.getClosestCollision();

    Vector3f worldPosOfContact = closestCollision.getContactPoint();

    Spatial sphereToUse = sphereGeom;

    // Vector3f normNormal =

    // closest.getContactNormal();

    // Quaternion q =

    // new Quaternion();

    // q.lookAt(

    // normNormal,

    // Vector3f.UNIT_Y );

    // Quaternion parentRotInversion =

    // sphereNode.getParent().getWorldRotation().inverse();

    // q =

    // parentRotInversion.mult( q );

    // sphereNode.setLocalRotation( q );

    Vector3f localContact = sphereToUse.getParent().worldToLocal(

    worldPosOfContact, null);

    // Vector3f localPointOnNormal =

    // worldContact.add( normNormal.mult( 1000f

    // // getting far point for low precision

    // ) );

    // keep sphereNode’s scale of 1,1,1 regardless of inherited scale

    Vector3f parentScale = sphereToUse.getParent().getWorldScale()

    .clone();

    // can never be too careful :))

    final Vector3f wantedSphereScale = Vector3f.UNIT_XYZ.clone();

    sphereToUse.setLocalScale(wantedSphereScale.divide(parentScale));

    // XXX: when non-uniform scale fix code is applied(r7291), this

    // fails?:

    // but it’s probably due to worldToLocal ?

    // undo that fix to see this working fine

    sphereToUse.setLocalTranslation(localContact);

    // I believe the fix is good, though I think it needs to be applied

    // also somewhere else too

    }

    }

    }

    [/java]

    EDIT: Note: it works when sphereParent has no scale or uniform scale set, currently it’s non-uniform, if you want to see it work, with uniform scale, obviously change this line no.69 :

    [java]sphereParent.setLocalScale(8f, 1.9f, 3f);[/java]

    to this:

    [java]sphereParent.setLocalScale(3f, 3f, 3f);[/java]
2 Likes

… that class is such a mess …



You’re right the worldToLocal method is returning incorrect results. To prove that, you can keep calling worldToLocal then localToWorld on the same vector and you will keep getting different results each time.



Not only did the actual transform concatenation did not follow convention, but the transform method and its inverse are using a completely different order of operations! In any case, I fixed this issue as well.



Big thanks for finding all those critical bugs :slight_smile:

1 Like

[java]public Vector3f transformInverseVector(final Vector3f in, Vector3f store){

if (store == null)

store = new Vector3f();

// in.subtract(translation, store).divideLocal(scale);

// rot.inverse().mult(store, store);

in.subtract(translation, store);

rot.inverse().mult(store, store);

store.divideLocal(scale);

return store;

}[/java]

omg I’m so happy I figured this out by myself, but I cannot take credit(EDIT:I mean I cannot prove that I figured it out by myself lol) for it because you already did it, no matter I are happy looool monkey happy :))

Thank YOU! and Happy Easter and all :wink:

I’m just going to put this out there, perhaps some of the bugs fixed in jme3 are fixable in jme2 also ?

I took a quick looksie at worldToLocal in here in jme2 branch:

http://code.google.com/p/jmonkeyengine/source/browse/branches/2.0/src/com/jme/scene/Spatial.java

[java]public Vector3f worldToLocal(final Vector3f in, final Vector3f store) {

in.subtract(getWorldTranslation(), store).divideLocal(getWorldScale());

getWorldRotation().inverse().mult(store, store);

return store;

}

[/java]

appears to need this fix at least…

EDIT: but this fix would only work when r7291 is applied also, couldn’t find out (in such short notice) where that one is in jme2

Peace out :wink:

EDIT2: I was looking at the wrong branch, that was jme 2.0.x

this is jme2.1

http://code.google.com/p/jmonkeyengine/source/browse/branches/jme2.1/src/com/jme/scene/Spatial.java

though it looks the same in worldToLocal



EDIT3: it was first introduced here (in rev 3003): http://code.google.com/p/jmonkeyengine/source/browse/trunk/src/com/jme/scene/Spatial.java?spec=svn3003&r=3003

Respectfully,

signing off :slight_smile:

jME3 already has many bugs fixed from jME2, and we didn’t commit those back. If we apply this fix we will have to fix all the others as well.

1 Like

jME2 is community supported, we wont touch it anymore.

1 Like

Sorry for brining this thread up but it think my problem fits in here?



I have an issue with worldToLocal and transformInverseVector to be precise. I’m getting an NPE very rarely in my application.



[java]

23.02.2012 11:02:24 class com.jme3.app.AppTask invoke()

SCHWERWIEGEND: Exception

java.lang.NullPointerException

at com.jme3.math.Transform.transformInverseVector(Transform.java:257)

at com.jme3.scene.Spatial.worldToLocal(Spatial.java:720)

at mars.auv.BasicAUV$2.call(BasicAUV.java:1556)

at mars.auv.BasicAUV$2.call(BasicAUV.java:1547)

at com.jme3.app.AppTask.invoke(AppTask.java:142)

at com.jme3.app.Application.update(Application.java:581)

at com.jme3.app.SimpleApplication.update(SimpleApplication.java:225)

at com.jme3.system.awt.AwtPanelsContext.updateInThread(AwtPanelsContext.java:157)

at com.jme3.system.awt.AwtPanelsContext.access$100(AwtPanelsContext.java:13)

at com.jme3.system.awt.AwtPanelsContext$AwtPanelsListener.update(AwtPanelsContext.java:37)

at com.jme3.system.lwjgl.LwjglOffscreenBuffer.runLoop(LwjglOffscreenBuffer.java:123)

at com.jme3.system.lwjgl.LwjglOffscreenBuffer.run(LwjglOffscreenBuffer.java:147)

at java.lang.Thread.run(Thread.java:662)

[/java]



For some reasons the inverse doesn’t exist for the quaternion

So maybe there should be a check for it?



[java]

public Vector3f transformInverseVector(final Vector3f in, Vector3f store){

if (store == null)

store = new Vector3f();



// The author of this code should look above and take the inverse of that

// But for some reason, they didnt …

// in.subtract(translation, store).divideLocal(scale);

// rot.inverse().mult(store, store);



in.subtract(translation, store);

rot.inverse().mult(store, store); <–NPE

store.divideLocal(scale);



return store;

}

[/java]



You could now argue if the inverse doesn’t exist than you fucked up something else big. Since i’m not an quaternion expert i don’t now what it means that the inverse doesn’t exist or if it’s a bad thing. But the only thing i’m doing is using worldtoLocal.



[java]auv_node.worldToLocal(volume_center_fin, volume_center_local);[/java]

the quaternion inverse method returns null when the norm of the quaternion is negative or zero.

From what I read, the Math rule is that a quaternion has an inverse only if it’s not 0,0,0,0.

I guess the norm computation in quaternion.inverse() is here to check this, but something bothers me :

The norm of a quaternion is sqrt(ww+xx+yy+zz).

The norm() function returns ww+xx+yy+zz. If you get over the fact that it’s wrong…this obviously can have a negative value (or am i wrong?) but sqrt(ww+xx+yy+zz) cannot. (square root is positive). So quaternions that have ww+xx+yy+zz negative will throw your exception when they shouldn’t.



So…maybe this is intended to not compute the square root which is expensive, but then the test should be if (norm==0.0f) return null; and not norm > 0.0f.



Or there is an error in the norm computation.

@Momoko_Fan is that intended?



@Lockhead Or…your rotation quaternion is 0,0,0,0 which is very weird.

@nehon, you may need another cup of coffee. :slight_smile:



I’m not even sure what a sqrt() would be for a negative number. What multiplied by itself would be negative? Just not possible.



This function:

ww+xx+yy+zz



Will never return negative. Can you think of any numbers when multiplied by themselves that would produce a negative? Will adding them together make them go backwards?



I can’t say what may or may not be wrong with quaternion but those functions are always dealing in positive numbers.

@pspeed said:
@nehon, you may need another cup of coffee. :)

I'm not even sure what a sqrt() would be for a negative number. What multiplied by itself would be negative? Just not possible.

This function:
w*w+x*x+y*y+z*z

Will never return negative. Can you think of any numbers when multiplied by themselves that would produce a negative? Will adding them together make them go backwards?

I can't say what may or may not be wrong with quaternion but those functions are always dealing in positive numbers.

hehehe true :D

I told you that the moment i was considered the math guy of the project we were doomed!

So @Lockhead this let you with the 0,0,0,0 rotation case...
@Lockhead said:
For some reasons the inverse doesn't exist for the quaternion
So maybe there should be a check for it?
You could now argue if the inverse doesn't exist than you fucked up something else big. Since i'm not an quaternion expert i don't now what it means that the inverse doesn't exist or if it's a bad thing. But the only thing i'm doing is using worldtoLocal.


My guess is that in these cases your Quaternion is somehow 0,0,0,0. If you are not specifically setting a bad quaternion then this is a sign that you are doing something really really bad somewhere. Something I can't even imagine at the moment.

Edit: a little bit of debugging (or dumping values to console) might show you what is happening.

Maybe initializing a quaternion with Quaternion.ZERO instead of Quaternion.IDENTITY?

Thanks for the responses. Will try to dig deeper after the weekend. For what i can say now the Quaternion is NaN at some point.

Maybe the physics has to do something with it since it’s the only part that is moving and rotating the node. I’m only applying forces and when i want to move the node than i’m using the RigidBodyControl (which i’m not doing at all when trying to produce this problem).

Will try to use native Bullet and see what happens. Unfortunately I’m stuck to jBullet because of the 64Bit Issues of native Bullet. It would be too much of a hassle to force 32JVM on my “cutomers”.

Long story short. I get and Infinity sometimes when colliding my Model with a Ray.



The problematic part is in BIHNode in the public final int intersectWhere(Ray r,Matrix4f worldMatrix, BIHTree tree, float sceneMin, float sceneMax, CollisionResults results) method (~:402).



[java]

for (int i = node.leftIndex; i <= node.rightIndex; i++) {

tree.getTriangle(i, v1, v2, v3);



float t = r.intersects(v1, v2, v3);

if (!Float.isInfinite(t)) {

if (worldMatrix != null) {

worldMatrix.mult(v1, v1);

worldMatrix.mult(v2, v2);

worldMatrix.mult(v3, v3);

float t_world = new Ray(o, d).intersects(v1, v2, v3);

t = t_world;

}



Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null);

Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o);

float worldSpaceDist = o.distance(contactPoint);



CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist);

cr.setContactNormal(contactNormal);

cr.setTriangleIndex(tree.getTriangleIndex(i));

results.addCollision(cr);

cols++;

}

}

[/java]



At the beginning we have our first intersection [java]float t = r.intersects(v1, v2, v3);[/java]. We check than for infinity [java]if (!Float.isInfinite(t)) {[/java]. So we don’t add any “bad” ContactPoints. After that we use the worldMatrix to change the vectors and intersect again with the changed vectors. But here i get sometimes and Infinity and because this one will not be checked it will be added as a ContactPoint. I checked the worldMatrix and it doesn’t contain any NaNs or Infs.



I checked the intersects Method of the Ray and it isn’t the parallel case but the last return. The Edge case? Or is it then outside of the triangle? If so then why is it then interpreted as an ContactPoint? And if it’s the edge then why and infinity as the ContactPoint? Shouldn’t it be possible to get the ContactPoint correctly?



Either way, too much math in the morning ;). Maybe someone could explain me how i should interpret infinite ContactPoints (Parrallel,Edge,Outside).



And sorry for digging so deep ;).

It doesn’t exactly make sense to me why the first collision would succeed and then the second will fail. Are you sure the world matrix is correct? NaN/Inf are not the only thing making a broken matrix

Since you’ve drilled in this far, can you show us the value of the worldMatrix in the case where you get bad contact points?



As momoko hints, I suspect this matrix is bad.

Here an quick and dirty code piece.

With this matrix and vectors i get an inf.

[java]

Matrix4f matrix = new Matrix4f(0.099684946f, 0.003476259f, 0.007129367f, -0.05035142f, -0.0035146326f, 0.099937364f, 4.1346974E-4f, -0.021245062f, -0.0071105273f, -6.6273817E-4f, 0.09974468f, -0.023290642f, 0.0f, 0.0f, 0.0f, 1.0f);

Vector3f start = new Vector3f(1.9252679f, -49.951576f, -2.914092f);

Vector3f dir = new Vector3f(-0.035146322f, 0.9993737f, 0.0041346983f);

Ray test = new Ray(start, dir);

Vector3f v1 = new Vector3f(2.154625f, -0.832799f, -2.879551f);

Vector3f v2 = new Vector3f(2.154625f, -0.832799f, -2.534256f);

Vector3f v3 = new Vector3f(-1.67098f, -0.832798f, -2.879551f);

Vector3f v4 = Vector3f.ZERO;

Vector3f v5 = Vector3f.ZERO;

Vector3f v6 = Vector3f.ZERO;

float t_world = 0f;

float t = test.intersects(v1, v2, v3);

if (!Float.isInfinite(t)) {

matrix.mult(v1, v4);

matrix.mult(v2, v5);

matrix.mult(v3, v6);

t_world = test.intersects(v4, v5, v6);

}

[/java]

Just eyeballing it, that matrix looks a bit messed up to me. Where does it come from? (Admittedly, my ability to eyeball a 4x4 matrix is very rusty.)

It’s the cachedWorldMat from the Geometry Class. And it will be computed from the worldTransform in the computeWorldMatrix method.

Yeah, I mean that I’d be curious to know what that geometry thinks its translation, scale, and orientation are.