From 234545d39e9748b26ca766ddda0463ccbab8a2a8 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Sep 2025 12:37:25 +0200 Subject: [PATCH 1/7] Added `worldToLocal` method to `Spatial` for quaternion transformations and corresponding test --- .../src/main/java/com/jme3/scene/Spatial.java | 16 ++++++++++ .../test/java/com/jme3/scene/SpatialTest.java | 31 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 2fe1775d3e..a65e8748bc 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -986,6 +986,22 @@ 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); + } + store.multLocal(worldTransform.getRotation().inverse()); + return store; + } + /** * getParent retrieves this node's parent. If the parent is * null this is the root node. diff --git a/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java index 038bdc3fc8..e47204ca53 100644 --- a/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java +++ b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java @@ -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; @@ -119,4 +122,32 @@ 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.assertEquals(worldTranslation,testNode.getWorldTranslation()); + Assert.assertEquals(worldRotation,testNode.getWorldRotation()); + + nodeB.attachChild(testNode); + + Assert.assertNotEquals(worldTranslation,testNode.getWorldTranslation()); + Assert.assertNotEquals(worldRotation,testNode.getWorldRotation()); + + testNode.setLocalTranslation(nodeB.worldToLocal(worldTranslation,null)); + Assert.assertEquals(worldTranslation,testNode.getWorldTranslation()); + + testNode.setLocalRotation(nodeB.worldToLocal(worldRotation,null)); + Assert.assertEquals(worldRotation,testNode.getWorldRotation()); + } } From 3a43016fad256df44649989525e3ce0817ab9999 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Fri, 19 Sep 2025 12:41:12 +0200 Subject: [PATCH 2/7] Fixed `worldToLocal` method to correctly handle store parameter for quaternion transformations. --- jme3-core/src/main/java/com/jme3/scene/Spatial.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index a65e8748bc..deb39a6ab8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -997,6 +997,8 @@ public Quaternion worldToLocal(final Quaternion in, Quaternion store){ checkDoTransformUpdate(); if(store == null){ store=new Quaternion(in); + }else{ + store.set(in); } store.multLocal(worldTransform.getRotation().inverse()); return store; From baae9dff5986da79161e3e4e6dd1444419771144 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Sat, 20 Sep 2025 20:34:14 +0200 Subject: [PATCH 3/7] Replaced `Assert.assertEquals` with `isSimilar` in `SpatialTest` for enhanced floating-point precision checks. --- .../src/test/java/com/jme3/scene/SpatialTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java index e47204ca53..48c9147920 100644 --- a/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java +++ b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java @@ -136,18 +136,19 @@ public void testTransferToOtherNode(){ Vector3f worldTranslation = testNode.getWorldTranslation().clone(); Quaternion worldRotation = testNode.getWorldRotation().clone(); - Assert.assertEquals(worldTranslation,testNode.getWorldTranslation()); - Assert.assertEquals(worldRotation,testNode.getWorldRotation()); + Assert.assertTrue(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f)); + Assert.assertTrue(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f)); nodeB.attachChild(testNode); - Assert.assertNotEquals(worldTranslation,testNode.getWorldTranslation()); - Assert.assertNotEquals(worldRotation,testNode.getWorldRotation()); + Assert.assertFalse(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f)); + Assert.assertFalse(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f)); testNode.setLocalTranslation(nodeB.worldToLocal(worldTranslation,null)); - Assert.assertEquals(worldTranslation,testNode.getWorldTranslation()); + Assert.assertTrue(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f)); testNode.setLocalRotation(nodeB.worldToLocal(worldRotation,null)); - Assert.assertEquals(worldRotation,testNode.getWorldRotation()); + System.out.println(testNode.getWorldRotation()); + Assert.assertTrue(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f)); } } From aae6052af342a5f7e6a87315a3da7f723ca5b3aa Mon Sep 17 00:00:00 2001 From: zzuegg Date: Sat, 20 Sep 2025 20:35:02 +0200 Subject: [PATCH 4/7] removed the need to allocate a temporary object. (Quaternion.inverse()) --- .../src/main/java/com/jme3/scene/Spatial.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index deb39a6ab8..2a8759ef50 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -1000,8 +1000,21 @@ public Quaternion worldToLocal(final Quaternion in, Quaternion store){ }else{ store.set(in); } - store.multLocal(worldTransform.getRotation().inverse()); + 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, 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; } /** From 3048352387e4b0220ace3886c0e4f552a1f1fd86 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Sat, 20 Sep 2025 20:37:03 +0200 Subject: [PATCH 5/7] Formatted multiline method call in `Spatial` for improved readability. --- jme3-core/src/main/java/com/jme3/scene/Spatial.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 2a8759ef50..6307541605 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -1007,7 +1007,9 @@ public Quaternion worldToLocal(final Quaternion in, Quaternion store){ //Second option is to normalize manually float norm = rotation.norm(); - store.multLocal(rotation.getX()*-norm, rotation.getY()*-norm, rotation.getZ()*-norm, rotation.getW()*norm); + store.multLocal( + rotation.getX()*-norm, rotation.getY()*-norm, + rotation.getZ()*-norm, rotation.getW()*norm); return store; //Third option is to temporarily change the parent's quaternion. More expensive then option 2, From 726ea52143d11641af223ab33f6b90cd537ceff1 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Sat, 20 Sep 2025 20:37:34 +0200 Subject: [PATCH 6/7] Formatted multiline method call in `Spatial` for improved readability. --- jme3-core/src/main/java/com/jme3/scene/Spatial.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 6307541605..53f8e9f326 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -1007,9 +1007,8 @@ public Quaternion worldToLocal(final Quaternion in, Quaternion store){ //Second option is to normalize manually float norm = rotation.norm(); - store.multLocal( - rotation.getX()*-norm, rotation.getY()*-norm, - rotation.getZ()*-norm, rotation.getW()*norm); + store.multLocal(rotation.getX()*-norm, rotation.getY()*-norm, + rotation.getZ()*-norm, rotation.getW()*norm); return store; //Third option is to temporarily change the parent's quaternion. More expensive then option 2, From 2eb11cf389a1f97539e568760ea0a5d021f23da7 Mon Sep 17 00:00:00 2001 From: zzuegg Date: Tue, 23 Sep 2025 03:59:52 +0200 Subject: [PATCH 7/7] Refactored `worldToLocal` in `Spatial` to utilize `TempVars` for optimized quaternion handling and removed redundant comments. --- .../src/main/java/com/jme3/scene/Spatial.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 53f8e9f326..b10a700e7a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -1000,22 +1000,12 @@ public Quaternion worldToLocal(final Quaternion in, Quaternion store){ }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, - rotation.getZ()*-norm, rotation.getW()*norm); + TempVars tempVars = TempVars.get(); + Quaternion worldRotation = tempVars.quat1.set(getWorldRotation()); + worldRotation.inverseLocal(); + store.multLocal(worldRotation); + tempVars.release(); 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; } /**