Skip to content
32 changes: 32 additions & 0 deletions jme3-core/src/main/java/com/jme3/scene/Spatial.java
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,38 @@ public Vector3f worldToLocal(final Vector3f in, final Vector3f store) {
return worldTransform.transformInverseVector(in, store);
}

/**
* Transforms the given quaternion from world space to local space relative to this object's transform.
*
* @param in the input quaternion in world space that needs to be transformed
* @param store an optional Quaternion to store the result; if null, a new Quaternion will be created
* @return the transformed quaternion in local space, either stored in the provided Quaternion or a new one
*/
public Quaternion worldToLocal(final Quaternion in, Quaternion store){
checkDoTransformUpdate();
if(store == null){
store=new Quaternion(in);
}else{
store.set(in);
}
Quaternion rotation = worldTransform.getRotation();
//can we be sure if the quaternion is normalized?
//if yes, the conjugate could be used, and the normalizing procedure skipped
//store.multLocal(-rotation.getX(), -rotation.getY(), -rotation.getZ(), rotation.getW();

//Second option is to normalize manually
float norm = rotation.norm();
store.multLocal(rotation.getX()*-norm, rotation.getY()*-norm,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right to me if you're trying to normalize and invert the rotation. From looking at Quaternion, it seems like norm should be changed like this:

norm = FastMath.invSqrt(norm) / norm;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally i agree, using jme math i have not yet managed to create a non normalized quaternion.

        for (int i = 0; i < 1000000; i++) {
            Quaternion quaternion=new Quaternion().fromAngles(FastMath.nextRandomFloat()*-360,FastMath.nextRandomFloat()*360,FastMath.nextRandomFloat()*360);
            Quaternion inverse = quaternion.inverse();
            float norm = quaternion.norm();
            //norm = FastMath.invSqrt(norm)/norm;
            System.out.println(norm);
            quaternion.set(quaternion.getX()*-norm, quaternion.getY()*-norm, quaternion.getZ()*-norm, quaternion.getW()*norm);
            if(!quaternion.isSimilar(inverse,0.000001f)){
                System.out.println("Fail");
                System.out.println(quaternion);
                System.out.println(inverse);
                System.exit(1);
            }
        }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could use temp vars and use the existing math functions. I probably prefer that. Considering that this code is probably never executed in the "hot loop"

rotation.getZ()*-norm, rotation.getW()*norm);
return store;

//Third option is to temporarily change the parent's quaternion. More expensive then option 2,
//but produces more accurate results. compared to option 2. (Error still below 1e-6f)
//store.multLocal(rotation.inverseLocal());
//rotation.inverseLocal();
//return store;
}

/**
* <code>getParent</code> retrieves this node's parent. If the parent is
* null this is the root node.
Expand Down
32 changes: 32 additions & 0 deletions jme3-core/src/test/java/com/jme3/scene/SpatialTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
*/
package com.jme3.scene;

import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.control.UpdateControl;
import org.junit.Assert;
import org.junit.Test;
Expand Down Expand Up @@ -119,4 +122,33 @@ public void testAddControlAt() {
Assert.assertEquals(testSpatial, control1.getSpatial());
Assert.assertEquals(testSpatial, control2.getSpatial());
}

@Test
public void testTransferToOtherNode(){
Node nodeA = new Node("nodeA");
Node nodeB = new Node("nodeB");
Node testNode=new Node("testNode");
nodeA.setLocalTranslation(-1,0,0);
nodeB.setLocalTranslation(1,0,0);
nodeB.rotate(0,90* FastMath.DEG_TO_RAD,0);
testNode.setLocalTranslation(1,0,0);
nodeA.attachChild(testNode);
Vector3f worldTranslation = testNode.getWorldTranslation().clone();
Quaternion worldRotation = testNode.getWorldRotation().clone();

Assert.assertTrue(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f));
Assert.assertTrue(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f));

nodeB.attachChild(testNode);

Assert.assertFalse(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f));
Assert.assertFalse(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f));

testNode.setLocalTranslation(nodeB.worldToLocal(worldTranslation,null));
Assert.assertTrue(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f));

testNode.setLocalRotation(nodeB.worldToLocal(worldRotation,null));
System.out.println(testNode.getWorldRotation());
Assert.assertTrue(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f));
}
}
Loading