# [Solved]How to get the inverse of a Transform OR Node.getWorldToLocalMatrix ? jme3 r7259

Simple: I have a Line in World coords and I want it to look the same (same pos/rotation/scale) after I add it to a node N that has its own transformations(node’s own pos/rotation/scale).

Assumption: I need to transform this Line such that when applying the node N’s transform it will look exactly the same as before the Line was transformed. By transform I mean scale/translation/rotation

Example:

ie. nodes:

rootNode->N->nodeLine->geoLine

nodeLine’s transform and N’s transform nullify such that it would appear that geoLine is the child of rootNode ie. rootNode->geoLine

Detailed:

For example I have 2 vectors in world coords (due to Ray/collideWith results) and I want to draw a Line between them. And so I do and it looks great if I add them to rootNode and rootNode has no transformations (ie. Transform.IDENTITY) but if I want to add that line to a subnode of rootNode which has rotation/scale/translations I need to transform my Line before adding it to that subnode such that the newly transformed line has exactly the inverse transformation when compared to the subnode so that when inside subnode these 2 transformations would combine and cause my Line to look exactly the same as if it was in the World coords.

I did notice that there is a Node.worldToLocal(in,out) for Vector3f though, and Transform.transformInverseVector(in,out) for Vector3f, these don’t seem to work for this case of Line

I’d need a Transform.inverse() ?

Anyway I did try this:

Transform t = subRoot.getLocalTransform().clone();

nodeLine.setLocalRotation(t.getRotation().inverse()); //also tried opposite()

nodeLine.setLocalTranslation(t.getTranslation().negate());

nodeLine.setLocalScale(t.getScale().negate());

then I created the Line using world coords

where nodeLine was a node containing the Line in world coords and subRoot was the node I was trying to attach the line to such that it would look exactly the same after addition; subRoot had it’s own transformation which I was trying to have in exact opposite in nodeLine such that they would nullify so my Line would look as if it was in world coords (or part of rootNode[assuming rootNode had no transforms] even though it was now part of subRoot[which had its own transforms])

here’s the failing code(just hover mouse over that long box):

rootNode->subRoot->collidables->orange Box

rootNode->subRoot->nodeLine->Line

and I’m trying to transform nodeLine in exact opposition of subRoot so that it would appear as if nodeLine is part of rootNode

When code works, those randomcolored lines would be normals on that long box

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

`pre type="java"`
package org.jme3.tests;

import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResult;

import com.jme3.collision.CollisionResults;

import com.jme3.font.BitmapText;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

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

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Line;

import com.jme3.system.AppSettings;

public class TranslationWickedtry extends SimpleApplication {

private Node collidables;

private Node nodeLine;

private Node subRoot;

private final static String mapToggleSubNodeTransform = “mapToggleSubNodeTransform”;

private static final float lineWidth = 5f;

private BitmapText helloText;

public static void main(String[] args) {

TranslationWickedtry app = new TranslationWickedtry();

AppSettings aps = new AppSettings(true);

aps.setVSync(true);

app.setShowSettings(false);

app.setSettings(aps);

app.start();

}

@Override

public void simpleInitApp() {

flyCam.setDragToRotate(true);

flyCam.setMoveSpeed(20f);

cam.setLocation(new Vector3f(0f, 0f, 52f));

// cam.setLocation(new Vector3f(61.42587f, -145.20616f, 13.512871f));

// cam.setRotation(new Quaternion(0.5186105f, 0.4658943f, -0.41162565f,

// 0.5869838f));

// cam.setDirection(new Vector3f(0.1199981f, -0.99238f, 0.027971268f));

subRoot = new Node();

rootNode.attachChild(subRoot);

subRoot.setLocalScale(0.4f, 11f, 0.6f);// must be all positive

subRoot.setLocalRotation(new Quaternion().fromAngles(

subRoot.setLocalTranslation(3f, -8f, -12f);

nodeLine = new Node();

subRoot.attachChild(nodeLine);

/** create four colored boxes and a floor to shoot at: */

collidables = new Node(“collidables”);

subRoot.attachChild(collidables);

Box meshBox = new Box(new Vector3f(-2, 0, 1), 1, 1, 1);

Geometry geoBox = new Geometry(“Boxy”, meshBox);

Material matBox = new Material(assetManager,

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

matBox.setColor(“Color”, ColorRGBA.Orange);

geoBox.setMaterial(matBox);

collidables.attachChild(geoBox);

// transformSubNode();

Node coord = new Node();

attachCoordinateAxes(Vector3f.ZERO, coord);

rootNode.attachChild(coord);

KeyInput.KEY_SPACE));

// ========

helloText = new BitmapText(guiFont, false);

helloText.setSize(guiFont.getCharSet().getRenderedSize());

helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);

helloText.setText(“press SPACE to toggle subNode’s transform”);

guiNode.attachChild(helloText);

}

private void transformSubNode() {

collidables.setLocalTranslation(11, 17, -29);

collidables.scale(2.3f, 4.2f, 7.1f);

}

private final ActionListener actionListener = new ActionListener() {

@SuppressWarnings(“synthetic-access”)

@Override

public void onAction(String name, boolean isPressed, float tpf) {

do {

if ((mapToggleSubNodeTransform == name) && (isPressed)) {

// FIXME: implement Transform.equals() or .isIdentity()?

// if

// (subNode.getLocalTransform().equals(Transform.IDENTITY))

// {

// subNode.getLocalRotation().equals(o)

if (collidables.getLocalTransform().getTranslation().getX() == 0) {

helloText.setText(“subNode is transformed”);

transformSubNode();

} else {

helloText.setText(“subNode is NOT transformed”);

collidables.setLocalTransform(Transform.IDENTITY);

}

break;

}

} while (false);

}

};

@Override

public void simpleUpdate(float tpf) {

Vector3f origin = cam.getWorldCoordinates(

inputManager.getCursorPosition(), 0.0f);

Vector3f direction = cam

.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f)

.subtractLocal(origin).normalizeLocal();

Ray ray = new Ray(origin, direction);

CollisionResults results = new CollisionResults();

collidables.collideWith(ray, results);

if (results.size() > 0) {

CollisionResult closest = results.getClosestCollision();

// world coord of the contact

Vector3f normalStartPoint = closest.getContactPoint().clone();

// the direction of the normal, normalized already:

Vector3f normalDirection = closest.getContactNormal();

if (!normalDirection.isUnitVector()) {

// was not normalized?! impossible

throw null;

}

// normalVec is a point on the `normal to the surface`

.mult(10f));

// these 2 fail too

// normalStartPoint = subRoot.worldToLocal(normalStartPoint, null);

// normalEndPoint = subRoot.worldToLocal(normalEndPoint, null);

// subRoot.getWorldTransform().

// the vector perpendicular on both `contact` and `normal` vectors

// Vector3f upVec = contact.cross(normalVec).normalizeLocal();

// XXX: I fail to get the inverse transform for nodeLine

Transform t =

// new Transform();

subRoot.getLocalTransform().clone();

// // // subRoot.getLocalToWorldMatrix(store)

// // // subRoot.getW

nodeLine.setLocalRotation(t.getRotation().inverse());

nodeLine.setLocalTranslation(t.getTranslation().negate());

nodeLine.setLocalScale(t.getScale().negate());

// nodeLine

// .getLocalTransform()

// .clone()

// t.interpolateTransforms(subRoot.getWorldTransform(),

// subRoot.getLocalTransform(), 1);

// nodeLine.setLocalTransform(t);

// nodeLine.setLocalTransform(t);

// normalStartPoint = nodeLine.worldToLocal(normalStartPoint, null);

// normalEndPoint = nodeLine.worldToLocal(normalEndPoint, null);

Line line = new Line(normalStartPoint, normalEndPoint);

Geometry geoLine = new Geometry(“line1”, line);

Material mark_mat = new Material(assetManager,

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

mark_mat.setColor(“Color”, ColorRGBA.randomColor());

geoLine.setMaterial(mark_mat);

nodeLine.attachChild(geoLine);// nodeLine is not transformed

// rootNode.attachChild(geoLine);// rootNode is not transformed

// Q.assumedTrue(nodeLine.getLocalTransform().equals(Transform.IDENTITY));

}

}

private void attachCoordinateAxes(Vector3f pos, Node toNode) {

Arrow arrow = new Arrow(Vector3f.UNIT_X);

// make arrow thicker,

arrow.setLineWidth(lineWidth);

putShape(arrow, ColorRGBA.Red, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Y);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Green, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Z);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Blue, toNode).setLocalTranslation(pos);

}

private Geometry putShape(Mesh shape, ColorRGBA color, Node onNode) {

Geometry g = new Geometry(“coordinate axis”, shape);

Material mat = new Material(assetManager,

mat.setColor(“Color”, color);

g.setMaterial(mat);

onNode.attachChild(g);

// g.setCullHint(CullHint.Inherit);

return g;

}

}

`/pre`

Here’s a simplified version that only uses scale, and a method for getting the inverse of the scale:

`pre type="java"`
private Vector3f getInverseScale(final Vector3f scale) {

return new Vector3f(1 / scale.getX(), 1 / scale.getY(),

1 / scale.getZ());

}
`/pre`

http://i.imgur.com/1FhER.jpg

`pre type="java"`
package org.jme3.tests;

import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResult;

import com.jme3.collision.CollisionResults;

import com.jme3.font.BitmapText;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

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

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Line;

import com.jme3.system.AppSettings;

public class TranslationWickedtry extends SimpleApplication {

private Node collidables;

private Node nodeLine;

private Node subRoot;

private final static String mapToggleSubNodeTransform = “mapToggleSubNodeTransform”;

private static final float lineWidth = 5f;

private BitmapText helloText;

public static void main(String[] args) {

TranslationWickedtry app = new TranslationWickedtry();

AppSettings aps = new AppSettings(true);

aps.setVSync(true);

app.setShowSettings(false);

app.setSettings(aps);

app.start();

}

@Override

public void simpleInitApp() {

flyCam.setDragToRotate(true);

flyCam.setMoveSpeed(20f);

cam.setLocation(new Vector3f(0f, 0f, 52f));

// cam.setLocation(new Vector3f(61.42587f, -145.20616f, 13.512871f));

// cam.setRotation(new Quaternion(0.5186105f, 0.4658943f, -0.41162565f,

// 0.5869838f));

// cam.setDirection(new Vector3f(0.1199981f, -0.99238f, 0.027971268f));

subRoot = new Node();

rootNode.attachChild(subRoot);

subRoot.setLocalScale(0.4f, 11f, 0.6f);// must be all positive

// subRoot.setLocalScale(0.4f,11f,3f);

// subRoot.setLocalRotation(new Quaternion().fromAngles(

// subRoot.setLocalTranslation(3f, -8f, -12f);

nodeLine = new Node();

subRoot.attachChild(nodeLine);

/** create four colored boxes and a floor to shoot at:
/

collidables = new Node(“collidables”);

subRoot.attachChild(collidables);

Box meshBox = new Box(new Vector3f(0, 0, 0), 1, 1, 1);

Geometry geoBox = new Geometry(“Boxy”, meshBox);

Material matBox = new Material(assetManager,

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

matBox.setColor(“Color”, ColorRGBA.Orange);

geoBox.setMaterial(matBox);

collidables.attachChild(geoBox);

// transformSubNode();

Node coord = new Node();

attachCoordinateAxes(Vector3f.ZERO, coord);

rootNode.attachChild(coord);

KeyInput.KEY_SPACE));

// ========

helloText = new BitmapText(guiFont, false);

helloText.setSize(guiFont.getCharSet().getRenderedSize());

helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);

helloText.setText(“press SPACE to toggle subNode’s transform”);

guiNode.attachChild(helloText);

// ========

// // // // subRoot.getLocalToWorldMatrix(store)

// // // // subRoot.getW

// nodeLine.setLocalRotation(t.getRotation().inverse());

// nodeLine.setLocalTranslation(t.getTranslation().negate());

// nodeLine.setLocalScale(subRoot.getLocalScale().negate());

// System.out.println(subRoot.getLocalScale().negate());

// 0.2x=1 x=1/0.2

// x
1/x=1

// Vector3f subRootScale = subRoot.getLocalScale();

// nodeLine.setLocalScale(1 / subRootScale.getX(),

// 1 / subRootScale.getY(), 1 / subRootScale.getZ());

nodeLine.setLocalScale(getInverseScale(subRoot.getLocalScale()));

}

private Vector3f getInverseScale(final Vector3f scale) {

return new Vector3f(1 / scale.getX(), 1 / scale.getY(),

1 / scale.getZ());

}

private void transformSubNode() {

collidables.setLocalTranslation(11, 17, -29);

collidables.scale(2.3f, 4.2f, 7.1f);

}

private final ActionListener actionListener = new ActionListener() {

@SuppressWarnings(“synthetic-access”)

@Override

public void onAction(String name, boolean isPressed, float tpf) {

do {

if ((mapToggleSubNodeTransform == name) && (isPressed)) {

// FIXME: implement Transform.equals() or .isIdentity()?

// if

// (subNode.getLocalTransform().equals(Transform.IDENTITY))

// {

// subNode.getLocalRotation().equals(o)

if (collidables.getLocalTransform().getTranslation().getX() == 0) {

helloText.setText(“subNode is transformed”);

transformSubNode();

} else {

helloText.setText(“subNode is NOT transformed”);

collidables.setLocalTransform(Transform.IDENTITY);

}

break;

}

} while (false);

}

};

@Override

public void simpleUpdate(float tpf) {

Vector3f origin = cam.getWorldCoordinates(

inputManager.getCursorPosition(), 0.0f);

Vector3f direction = cam

.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f)

.subtractLocal(origin).normalizeLocal();

Ray ray = new Ray(origin, direction);

CollisionResults results = new CollisionResults();

collidables.collideWith(ray, results);

if (results.size() > 0) {

CollisionResult closest = results.getClosestCollision();

// world coord of the contact

Vector3f normalStartPoint = closest.getContactPoint().clone();

// the direction of the normal, normalized already:

Vector3f normalDirection = closest.getContactNormal();

if (!normalDirection.isUnitVector()) {

// was not normalized?! impossible

throw null;

}

// normalVec is a point on the `normal&nbsp;to&nbsp;the&nbsp;surface`

.mult(10f));

// these 2 fail too

// normalStartPoint = subRoot.worldToLocal(normalStartPoint, null);

// normalEndPoint = subRoot.worldToLocal(normalEndPoint, null);

//

// // subRoot.getWorldTransform().

//

// // the vector perpendicular on both `contact` and `normal`

// vectors

// Vector3f upVec = normalStartPoint.cross(normalEndPoint)

// .normalizeLocal();

// upVec = subRoot.worldToLocal(upVec, null);

// Quaternion q = new Quaternion();

// q.lookAt(normalDirection, upVec);

// XXX: I fail to get the inverse transform for nodeLine

// nodeLine

// .getLocalTransform()

// .clone()

// t.interpolateTransforms(subRoot.getWorldTransform(),

// subRoot.getLocalTransform(), 1);

// nodeLine.setLocalTransform(t);

// nodeLine.setLocalTransform(t);

// normalStartPoint = nodeLine.worldToLocal(normalStartPoint, null);

// normalEndPoint = nodeLine.worldToLocal(normalEndPoint, null);

Line line = new Line(normalStartPoint, normalEndPoint);

Geometry geoLine = new Geometry(“line1”, line);

Material mark_mat = new Material(assetManager,

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

mark_mat.setColor(“Color”, ColorRGBA.randomColor());

geoLine.setMaterial(mark_mat);

// geoLine.lookAt(normalEndPoint, upVec);

// geoLine.setLocalRotation(q);

nodeLine.attachChild(geoLine);// nodeLine is not transformed

// rootNode.attachChild(geoLine);// rootNode is not transformed

// Q.assumedTrue(nodeLine.getLocalTransform().equals(Transform.IDENTITY));

}

}

private void attachCoordinateAxes(Vector3f pos, Node toNode) {

Arrow arrow = new Arrow(Vector3f.UNIT_X);

// make arrow thicker,

arrow.setLineWidth(lineWidth);

putShape(arrow, ColorRGBA.Red, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Y);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Green, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Z);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Blue, toNode).setLocalTranslation(pos);

}

private Geometry putShape(Mesh shape, ColorRGBA color, Node onNode) {

Geometry g = new Geometry(“coordinate axis”, shape);

Material mat = new Material(assetManager,

mat.setColor(“Color”, color);

g.setMaterial(mat);

onNode.attachChild(g);

// g.setCullHint(CullHint.Inherit);

return g;

}

}

`/pre`

You’ll need to add some control on the node so that its parent transform are inverted on each update.

There is something similar in the BillboardControl, look at the update method.

Ok here, I can get the inverse of a transform now BUT something fails only when translation is different from 0,0,0 , otherwise it works perfectly. Is it just me or a jme3 bug?

the relevant methods:

`pre type="java"`
private Vector3f getInverseTranslation(final Vector3f inputTranslation) {

return inputTranslation.negate();

}

private Quaternion getInverseRotation(final Quaternion inputRotation) {

Quaternion q = inputRotation.inverse();

if (null == q) {

q = inputRotation;

}

return q;

}

private Vector3f getInverseScale(final Vector3f inputScale) {

return new Vector3f(1 / inputScale.getX(), 1 / inputScale.getY(),

1 / inputScale.getZ());

}
`/pre`

This is a screenshot of the fail (when you pressed Space and then Alt once, after you’ve moved mouse on the box in both positions to create the gray line normals):

Btw, green and orange are siblings of rootNode and only the gray normals are children of the node that contains the green box

like so:

rootNode->sub1Green->green box

rootNode->sub1Green->sub2Line->gray lines

rootNode->sub1orange->orange box

When you press LeftAlt, sub1Green (node) will get a non zero translation and this BUGs the sub2Line node such that when they both combine they don’t nullify (as they do when translation is 0 for sub1Green, their rotation & scale does nullify correctly though)

If you press LeftAlt again it will give sub1Green a translation of 0, and so all works well

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

and the code:

`pre type="java"`
package org.jme3.tests;

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.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

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

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Line;

import com.jme3.system.AppSettings;

public class TranslationWickedtry extends SimpleApplication {

private Node sub1orange;

private Node sub2Line;

private Node sub1Green;

private final static String mapToggleSubNodeTransform = “mapToggleSubNodeTransform”;

private final static String mapMoveGreen = “mapMoveGreen”;

private static final float lineWidth = 5f;

private BitmapText helloText;

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

TranslationWickedtry app = new TranslationWickedtry();

AppSettings aps = new AppSettings(true);

aps.setVSync(true);

app.setShowSettings(false);

app.setSettings(aps);

app.start();

}

@Override

public void simpleInitApp() {

flyCam.setDragToRotate(true);

flyCam.setMoveSpeed(20f);

sub1Green = new Node();

rootNode.attachChild(sub1Green);

sub1Green.setLocalScale(0.4f, 11f, 0.6f);// must be all positive

sub1Green.setLocalRotation(new Quaternion().fromAngles(

sub1Green.attachChild(makeUnitBox(“subRoot show Box”, ColorRGBA.Green));

sub1orange = new Node(“collidables”);

rootNode.attachChild(sub1orange);

sub1orange.attachChild(makeUnitBox(“orangeBox”, ColorRGBA.Orange));

Node coord = new Node();

attachCoordinateAxes(Vector3f.ZERO, coord);

rootNode.attachChild(coord);

KeyInput.KEY_SPACE));

mapMoveGreen);

// ========

helloText = new BitmapText(guiFont, false);

helloText.setSize(guiFont.getCharSet().getRenderedSize());

helloText.setLocalTranslation(300, helloText.getLineHeight() * 2, 0);

helloText

.setText(“press SPACE to toggle orange’s transFORMnPress LeftAlt to toggle Green’s transLATION”);

guiNode.attachChild(helloText);

// ========

sub2Line = new Node();

sub1Green.attachChild(sub2Line);

computeSub2Line();

}

private void computeSub2Line() {

// sub2Line is a child of sub1Green

// this will compute sub2Line so that it is inverse to sub1Green, such

// that when they are combined, sub2Line would appear to be sibling of

// sub1Green, that means their transforms nullify each other or so

sub2Line.setLocalScale(getInverseScale(sub1Green.getLocalScale()));

sub2Line.setLocalRotation(getInverseRotation(sub1Green

.getLocalRotation()));

sub2Line.setLocalTranslation(getInverseTranslation(sub1Green

.getLocalTranslation()));

}

private Geometry makeUnitBox(String name, ColorRGBA color) {

Box meshSRBox = new Box(new Vector3f(0, 0, 0), 1, 1, 1);

Geometry geoSRBox = new Geometry(name, meshSRBox);

Material matSRBox = new Material(assetManager,

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

matSRBox.setColor(“Color”, color);

geoSRBox.setMaterial(matSRBox);

return geoSRBox;

}

private Vector3f getInverseTranslation(final Vector3f inputTranslation) {

return inputTranslation.negate();

}

private Quaternion getInverseRotation(final Quaternion inputRotation) {

Quaternion q = inputRotation.inverse();

if (null == q) {

q = inputRotation;

}

return q;

}

private Vector3f getInverseScale(final Vector3f inputScale) {

return new Vector3f(1 / inputScale.getX(), 1 / inputScale.getY(),

1 / inputScale.getZ());

}

private void transformSubNode() {

sub1orange.setLocalTranslation(11, 17, -29);

sub1orange.scale(2.3f, 4.2f, 7.1f);

}

private final ActionListener actionListener = new ActionListener() {

@SuppressWarnings(“synthetic-access”)

@Override

public void onAction(String name, boolean isPressed, float tpf) {

do {

if ((mapToggleSubNodeTransform == name) && (isPressed)) {

// FIXME: implement Transform.equals() or .isIdentity()?

// if

// (subNode.getLocalTransform().equals(Transform.IDENTITY))

// {

// subNode.getLocalRotation().equals(o)

if (sub1orange.getLocalTransform().getTranslation().getX() == 0) {

// helloText.setText(“subNode is transformed”);

transformSubNode();

} else {

// helloText.setText(“subNode is NOT transformed”);

sub1orange.setLocalTransform(Transform.IDENTITY);

}

break;

}

if ((mapMoveGreen == name) && (isPressed)) {

if (sub1Green.getLocalTranslation().getX() == 0) {

sub1Green.setLocalTranslation(3f, -8f, -12f);

} else {

sub1Green.setLocalTranslation(Vector3f.ZERO.clone());

}

computeSub2Line();

break;

}

} while (false);

}

};

@Override

public void simpleUpdate(float tpf) {

Vector3f origin = cam.getWorldCoordinates(

inputManager.getCursorPosition(), 0.0f);

Vector3f direction = cam

.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f)

.subtractLocal(origin).normalizeLocal();

Ray ray = new Ray(origin, direction);

CollisionResults results = new CollisionResults();

sub1orange.collideWith(ray, results);

if (results.size() > 0) {

CollisionResult closest = results.getClosestCollision();

// world coord of the contact

Vector3f normalStartPoint = closest.getContactPoint().clone();

// the direction of the normal, normalized already:

Vector3f normalDirection = closest.getContactNormal();

if (!normalDirection.isUnitVector()) {

// was not normalized?! impossible

throw null;

}

// normalVec is a point on the `normal to the surface`

.mult(10f));

Line line = new Line(normalStartPoint, normalEndPoint);

Geometry geoLine = new Geometry(“a `normal` on contact surface”,

line);

Material mark_mat = new Material(assetManager,

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

mark_mat.setColor(“Color”, ColorRGBA.Gray);

geoLine.setMaterial(mark_mat);

sub2Line.attachChild(geoLine);// nodeLine is not transformed

}

}

private void attachCoordinateAxes(Vector3f pos, Node toNode) {

Arrow arrow = new Arrow(Vector3f.UNIT_X);

// make arrow thicker,

arrow.setLineWidth(lineWidth);

putShape(arrow, ColorRGBA.Red, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Y);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Green, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Z);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Blue, toNode).setLocalTranslation(pos);

}

private Geometry putShape(Mesh shape, ColorRGBA color, Node onNode) {

Geometry g = new Geometry(“coordinate axis”, shape);

Material mat = new Material(assetManager,

mat.setColor(“Color”, color);

g.setMaterial(mat);

onNode.attachChild(g);

return g;

}

}

`/pre`

No this can’t work like this.

To compute the inverted translation you need to multiply it by the inverse scale and the inverse rotation too.

You can use the Transform.inverseTransform(Vector3f v) method.

1 Like
nehon said:
You'll need to add some control on the node so that its parent transform are inverted on each update.
There is something similar in the BillboardControl, look at the update method.

I can't find the update method you're referring to?
BillboardControl.controlUpdate is empty
and the base class's AbstractControl.update method is just calling controlUpdate

Anyway, the code I posted in my previous post (right after you posted yours) is doing things right, but only fails when there's a local translation, in other words:
What local translation should I set to a node before I add it a child to another node, such that their combined translations are opposite to each other ? you would say:
`pre type="java"`
child.setLocalTranslation(parent.getLocalTranslation().negate());
`/pre`
right? because when I do
`pre type="java"`
`/pre`
but this must be not how parent/child combine ? they don't do ".add" ?!
Can you tell me how they combine? I looked at Transform.combineWithParent but I fail to understand it?
nehon said:
No this can't work like this.
To compute the inverted translation you need to multiply it by the inverse scale and the inverse rotation too.
You can use the Transform.inverseTransform(Vector3f v) method.

ok I'll see what I can do with
public Vector3f transformInverseVector(final Vector3f in, Vector3f store)
and process what you said

Yeah…took me a while to get that straight but here is the theory :

to combine a child’s and parent’s transforms you have to combine :

• child’s local translation (t) with parent’s world scale (S), world rotation ®, and world translation (T) : R*(t*S)+T
• child’s local rotation ® with parent’s world rotation ® : Rr ( and not rR the direction is important!!)
• child’s local scale (s) with parent’s world scale (S) : s*S

So as you can see scale and rotation are pretty intuitive…translation is less intuitive

btw that’s what is done in Transform.combineWithParent() method.
2 Likes

I was procrastinating this…understanding of how transforms are combined for spatials in order to get me the inverse transform…

This is in Spatial.updateWorldTransforms()

[java]worldTransform.set(localTransform);

worldTransform.combineWithParent(parent.worldTransform);[/java]

As I understand it this combine is being done from parent to child as I see in Spatial.checkDoTransformUpdate()

I notice that the parent.worldTransform.rotation is also being set/overwritten while processing the child(though worldTransform appears it is always being overwritten with localTransform in most places):

[java]/**

• Changes the values of this matrix acording to it’s parent. Very similar to the concept of Node/Spatial transforms.
• @param parent The parent matrix.
• @return This matrix, after combining.

*/

public Transform combineWithParent(Transform parent) {

scale.multLocal(parent.scale);

// rot.multLocal(parent.rot);

parent.rot.mult(rot, rot);

<strong>parent

.rot</strong>

.multLocal(translation)

.multLocal(parent.scale)

return this;

}[/java]

And this might be relevant to what I want, in CollisionShapeFactory.java

[java]/**
• returns the correct transform for a collisionshape in relation
• to the ancestor for which the collisionshape is generated
• @param spat
• @param parent
• @return

*/

private static Transform getTransform(Spatial spat, Spatial parent) {

Transform shapeTransform = new Transform();

Spatial parentNode = spat.getParent() != null ? spat.getParent() : spat;

Spatial currentSpatial = spat;

//if we have parents combine their transforms

while (parentNode != null) {

if (parent == currentSpatial) {

//real parent -> only apply scale, not transform

Transform trans = new Transform();

trans.setScale(currentSpatial.getLocalScale());

shapeTransform.combineWithParent(trans);

parentNode = null;

} else {

shapeTransform.combineWithParent(currentSpatial.getLocalTransform());

parentNode = currentSpatial.getParent();

currentSpatial = parentNode;

}

}

return shapeTransform;

}[/java]

I need to hit the sack, bbl

this is the latest development (although I’ll be working more on the methods, I may forget to update this though), it works now btw. with jme3 r7296

the gray lines are children of the node with the green box and are showing in the same place independent of it’s transform

I’m going to mark this as solved though it would be great to have some method to get the inverse of a Transform (have the method inside that class I mean) (going to work on getting one and maybe post it in Contribution depot when done)

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

[java]package org.jme3.tests;

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.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

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

import com.jme3.math.Vector3f;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.debug.Arrow;

import com.jme3.scene.shape.Box;

import com.jme3.scene.shape.Line;

import com.jme3.system.AppSettings;

public class TranslationWickedtry extends SimpleApplication {

private Node sub1orange;

private Node underGreen;

private Node sub1Green;

private final static String mapToggleSubNodeTransform = “mapToggleSubNodeTransform”;

private final static String mapMoveGreen = “mapMoveGreen”;

private final static String mapPauseG = “mapPauseG”;

private static final float lineWidth = 5f;

private BitmapText helloText;

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

TranslationWickedtry app = new TranslationWickedtry();

AppSettings aps = new AppSettings(true);

aps.setVSync(true);

app.setShowSettings(false);

app.setSettings(aps);

app.start();

}

@Override

public void simpleInitApp() {

assert rootNode.getWorldRotation().inverse() != null;

flyCam.setDragToRotate(true);

flyCam.setMoveSpeed(20f);

sub1Green = new Node();

rootNode.attachChild(sub1Green);

sub1Green.setLocalScale(0.4f, 11f, 0.6f);// must be all positive

sub1Green.setLocalRotation(new Quaternion().fromAngles(

sub1Green.attachChild(makeUnitBox(“subRoot show Box”, ColorRGBA.Green));

sub1orange = new Node(“collidables”);

rootNode.attachChild(sub1orange);

sub1orange.attachChild(makeUnitBox(“orangeBox”, ColorRGBA.Orange));

rootNode.attachChild(getNewCoordinateAxes(Vector3f.ZERO));

sub1orange.attachChild(getNewCoordinateAxes(Vector3f.ZERO));

sub1Green.attachChild(getNewCoordinateAxes(Vector3f.ZERO));

KeyInput.KEY_SPACE));

mapMoveGreen, mapPauseG);

// ========

helloText = new BitmapText(guiFont, false);

helloText.setSize(guiFont.getCharSet().getRenderedSize());

helloText.setLocalTranslation(300, helloText.getLineHeight() * 2, 0);

helloText

.setText(“press SPACE to toggle orange’s transFORMnPress LeftAlt to toggle Green’s transLATION”);

guiNode.attachChild(helloText);

// ========

underGreen = new Node();

sub1Green.attachChild(underGreen);

computeUnderGreen();

}

private void computeUnderGreen() {

// sub2Line is a child of sub1Green

// this will compute sub2Line so that it is inverse to sub1Green, such

// that when they are combined, sub2Line would appear to be sibling of

// sub1Green, that means their transforms nullify each other or so

// Node parent = underGreen.getParent();// parent==sub1Green

underGreen

.setLocalScale(getOpposingScale(underGreen, Vector3f.UNIT_XYZ));

underGreen.setLocalRotation(getOpposingRotation(underGreen,

Quaternion.IDENTITY));

underGreen.setLocalTranslation(getOpposingTranslation(underGreen,

Vector3f.ZERO));

}

private Geometry makeUnitBox(String name, ColorRGBA color) {

Box meshSRBox = new Box(new Vector3f(0, 0, 0), 1, 1, 1);

Geometry geoSRBox = new Geometry(name, meshSRBox);

Material matSRBox = new Material(assetManager,

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

matSRBox.setColor(“Color”, color);

geoSRBox.setMaterial(matSRBox);

return geoSRBox;

}

/**

• make sure this node is always at the same desired world position, no
• matter what parents’ transforms are affecting it<br>
• you must call this every time any parents’ transform change parents
• meaning anything above this node, ie. grandparents too

*
• @param forNode
• @param desiredWorldTranslation
• @return

*/

private Vector3f getOpposingTranslation(final Node forNode,

final Vector3f desiredWorldTranslation) {

Node parent = forNode.getParent();

if (null == parent) {

return desiredWorldTranslation;

} else {

return parent.worldToLocal(desiredWorldTranslation, null);

}

}

private Vector3f getOpposingTranslationL(final Node forNode,

final Vector3f desiredLocalTranslation) {

Node parent = forNode.getParent();

if (null == parent) {

return desiredLocalTranslation;

} else {

Vector3f originAsWorld = parent.localToWorld(Vector3f.ZERO, null);

localNow = parent.worldToLocal(localNow, null);

return localNow;

}

}

/**
• you must call this every time any of this Node’s parents change their
• transform

*
• @param forNode
• @param desiredLocalRotation
• ``````       will keep this local rotation, no matter what the inherited<br />
``````
• ``````       (from parents) rotations are happening<br />
``````
• @return

*/

private Quaternion getOpposingRotation(final Node forNode,

final Quaternion desiredLocalRotation) {

Node parent = forNode.getParent();

if (null == parent) {

return desiredLocalRotation;

} else {

Quaternion q = parent.getWorldRotation().inverse();

assert null != q;

if (null == q) {

throw null;// unexpected, recheck when happens

}

q.multLocal(desiredLocalRotation);

return q;

}

}

/**
• ignore all inherited scales from parents<br>
• you must call this every time parents’ scale changes though

*
• @param forNode
• @param desiredLocalScale
• ``````       ensure this scale stays fixed, disregarding all inherited<br />
``````
• ``````       scales from parents<br />
``````
• @return

*/

private Vector3f getOpposingScale(final Node forNode,

final Vector3f desiredLocalScale) {

Node parent = forNode.getParent();

if (null == parent) {

return desiredLocalScale;

} else {

return desiredLocalScale.divide(parent.getWorldScale());

}

}

private void transformSub1orange() {

sub1orange.setLocalTranslation(11, 17, -29);

sub1orange.scale(2.3f, 4.2f, 7.1f);

}

private final ActionListener actionListener = new ActionListener() {

@SuppressWarnings("synthetic-access")

@Override

public void onAction(String name, boolean isPressed, float tpf) {

do {

if ((mapPauseG == name) && (isPressed)) {

paused = !paused;

break;

}

if ((mapToggleSubNodeTransform == name) && (isPressed)) {

// FIXME: implement Transform.equals() or .isIdentity()?

// if

// (subNode.getLocalTransform().equals(Transform.IDENTITY))

// {

// subNode.getLocalRotation().equals(o)

if (sub1orange.getLocalTransform().getTranslation().getX() == 0) {

// helloText.setText("subNode is transformed");

transformSub1orange();

} else {

// helloText.setText("subNode is NOT transformed");

sub1orange.setLocalTransform(Transform.IDENTITY);

}

break;

}

if ((mapMoveGreen == name) && (isPressed)) {

if (sub1Green.getLocalTranslation().getX() == 0) {

sub1Green.setLocalTranslation(3f, -8f, -12f);

} else {

sub1Green.setLocalTranslation(Vector3f.ZERO.clone());

}

computeUnderGreen();

break;

}

} while (false);

}

};

@Override

public void simpleUpdate(float tpf) {

Vector3f origin = cam.getWorldCoordinates(

inputManager.getCursorPosition(), 0.0f);

Vector3f direction = cam

.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f)

.subtractLocal(origin).normalizeLocal();

Ray ray = new Ray(origin, direction);

CollisionResults results = new CollisionResults();

sub1orange.collideWith(ray, results);

if (results.size() > 0) {

CollisionResult closest = results.getClosestCollision();

// world coord of the contact

Vector3f collisionWorldPos = closest.getContactPoint().clone();

// the direction of the normal, normalized already:

Vector3f normalDirection = closest.getContactNormal();

if (!normalDirection.isUnitVector()) {

// was not normalized?! impossible

throw null;

}

// normalVec is a point on the `normal to the surface`

.mult(10f));

Line lineMesh = new Line(collisionWorldPos, worldPointOnNormal);

Geometry lineGeom = new Geometry("a `normal` on contact surface",

lineMesh);

Material lineMat = new Material(assetManager,

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

lineMat.setColor("Color", ColorRGBA.Gray);

lineGeom.setMaterial(lineMat);

underGreen.attachChild(lineGeom);

}

}

private Node getNewCoordinateAxes(Vector3f pos) {

Arrow arrow = new Arrow(Vector3f.UNIT_X);

Node toNode = new Node();

// make arrow thicker,

arrow.setLineWidth(lineWidth);

putShape(arrow, ColorRGBA.Red, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Y);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Green, toNode).setLocalTranslation(pos);

arrow = new Arrow(Vector3f.UNIT_Z);

arrow.setLineWidth(lineWidth); // make arrow thicker

putShape(arrow, ColorRGBA.Blue, toNode).setLocalTranslation(pos);

}

private Geometry putShape(Mesh shape, ColorRGBA color, Node onNode) {

Geometry g = new Geometry("coordinate axis", shape);

Material mat = new Material(assetManager,

mat.setColor("Color", color);

g.setMaterial(mat);

onNode.attachChild(g);

return g;

}

}

[/java]