Application order: rotation, translation

Hello,

my goal is to implement arrow representation (consisting of a cylinder and a cone) that connects two arbitrary Points. The two arrow spatials are grouped in an node. The cylinder and the cone are translated in a way with the result that they look like a arrow. Then I have written a method that computes the rotation between the two points. The idea was to apply this rotation to the arrow node. But this comes to the problem that the arrow spatials are invisible. I figured out that the problem does nor occur, when I skip the translation of the arrow spatials. But this has the drawback that the arrow does not look like an arrow.

A small example for this problem you can see here:
[java]
package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.debug.WireBox;
import com.jme3.scene.shape.Cylinder;

public class ArrowTest extends SimpleApplication {
Node arrowNode;
Material blueMat;
Material redMat;
Material greenMat;
Material whiteMat;
Vector3f middlePoint;
float length;
Quaternion arrowRot;

public static void main(String[] args) {
    ArrowTest app = new ArrowTest();
    app.start();
}

@Override
public void simpleInitApp() {
    flyCam.setMoveSpeed(100f);
    cam.setLocation(new Vector3f(0, 100, 200));
    cam.lookAt(new Vector3f(0, 0, 0), Vector3f.ZERO);
    
    arrowNode = new Node("ArrowNode");
    
    blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    blueMat.setColor("Color", ColorRGBA.Blue);
    redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    redMat.setColor("Color", ColorRGBA.Red);
    greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    greenMat.setColor("Color", ColorRGBA.Green);
    whiteMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    whiteMat.setColor("Color", ColorRGBA.White);
    
    Vector3f startPos = new Vector3f(0, 0, 0);
    Vector3f endPos = new Vector3f(50, 30, 0);
    
    BoundingBox bb1 = new BoundingBox(startPos, 10, 10, 10);
    WireBox wireBox1 = new WireBox();
    wireBox1.fromBoundingBox(bb1);
    wireBox1.setLineWidth(1.5f);
    Geometry bbGeom1 = new Geometry("bbGeom2", wireBox1);
    bbGeom1.setMaterial(blueMat);
    bbGeom1.setLocalTranslation(startPos);
    
    BoundingBox bb2 = new BoundingBox(endPos, 10, 10, 10);
    WireBox wireBox2 = new WireBox();
    wireBox2.fromBoundingBox(bb2);
    wireBox2.setLineWidth(1.5f);
    Geometry bbGeom2 = new Geometry("bbGeom2", wireBox2);
    bbGeom2.setMaterial(redMat);
    bbGeom2.setLocalTranslation(endPos);
    
    computeOrientation(startPos, endPos);
    
    buildArrow();
    
    arrowNode.setLocalTranslation(middlePoint);
    
    BoundingBox bounding = (BoundingBox) arrowNode.getWorldBound();
    WireBox wire = new WireBox();
    wire.fromBoundingBox(bounding);
    wire.setLineWidth(2f);
    Geometry wireGeom = new Geometry("wireGeom", wire);
    wireGeom.setMaterial(whiteMat);
    arrowNode.attachChild(wireGeom);
    
    arrowNode.rotate(arrowRot);
    
    rootNode.attachChild(bbGeom1);
    rootNode.attachChild(bbGeom2);
    rootNode.attachChild(arrowNode);
}

public void buildArrow() {
    float shaftRadius = 1.5f;
    float shaftLength = .8f*length;
    float tipRadius = 3f;
    float tipLength = .2f*length;
    
    Cylinder shaft = new Cylinder(32, 32, shaftRadius, shaftLength, true);
    Geometry shaftGeom = new Geometry("shaft", shaft);
    shaftGeom.setMaterial(blueMat);
    shaftGeom.setLocalTranslation(shaftGeom.getLocalTranslation().add(new Vector3f(0, 0, -tipLength/2)));
    
    Cylinder tip = new Cylinder(32, 32, tipRadius, 0, tipLength, true, false);
    Geometry tipGeom = new Geometry("tip", tip);
    tipGeom.setMaterial(redMat);
    Quaternion q = new Quaternion();
    q.fromAngleAxis(-180*FastMath.DEG_TO_RAD, new Vector3f(1,0,0));
    tipGeom.rotate(q);
    tipGeom.setLocalTranslation(tipGeom.getLocalTranslation().add(new Vector3f(0, 0, shaftLength/2)));
    
    arrowNode.attachChild(shaftGeom);
    arrowNode.attachChild(tipGeom);
}

public void computeOrientation(Vector3f fromPos, Vector3f toPos) {
    middlePoint = FastMath.interpolateLinear(0.5f, fromPos, toPos);
    length = fromPos.distance(toPos);
    arrowRot = computeRotationBetweenTwoPoints(fromPos, toPos);
}

public Quaternion computeRotationBetweenTwoPoints(Vector3f p1, Vector3f p2) {
    Vector3f z = Vector3f.UNIT_Z;
    Vector3f connectionVector = getConnectionVector(p1, p2);

    Vector3f a = z.cross(connectionVector);
    float w = FastMath.sqrt(FastMath.pow(z.length(), 2) * FastMath.pow(connectionVector.length(), 2)) + z.dot(connectionVector);
    Quaternion q = new Quaternion(a.x, a.y, a.z, w);

    return q;
}

public Vector3f getConnectionVector(Vector3f p1, Vector3f p2) {
    return new Vector3f(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z);
}

}
[/java]

The start and end point of the arrow are the center of two bounding boxes. For debug purposes I added a bounding box that visualized the bounding of the arrow’s spatials. By the help of this you can see that the rotation is applied to the arrow node; but the arrow spatials are not visible.
Did some know what I make wrong? What transformations do I need to connect the points by the arrow?

Honestly, I only got this far:
cam.lookAt(new Vector3f(0, 0, 0), Vector3f.ZERO);

That’s not right.

Also, a Quaternion is not an Angle-axis. I think your computation of the quaternion is wrong. I don’t know why not just http://hub.jmonkeyengine.org/javadoc/com/jme3/math/Quaternion.html#lookAt(com.jme3.math.Vector3f,%20com.jme3.math.Vector3f)

Specifically, this method looks wrong to me: computeRotationBetweenTwoPoints

…among various other small “huh?” stuff going on.

Thank you for answer. I just found the mistake.
The solution for the computation between two points I found here: http://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another
In my implementation I have forgotten to normalize the computed quaternion as pointed out in the stackoverflow post.

Concerning the cam.lookUp method: is [java]cam.lookAt(new Vector3f(0, 0, 0), cam.getUp());[/java] correct? And why is [java]cam.lookAt(new Vector3f(0, 0, 0), Vector3f.ZERO);[/java] not right?

for a visual demonstration, pretend at rest you are facing forwards. And you wish to look behind you, you can turn around, and you can either be standing upright, on your head, laying on the ground etc You need an extra command, like “stand up”, or orient yourself perpendicular to the ground (cam.getUp). Now try to turn around but orient yourself around nothing, how are you gonna achieve this? Similarly with jME, it needs to know this information, lookAt does a few cross products to get a Quaternion with 3 orthogonal axes, passing a zero vector will make them all zero (except z). If I remember it does something like:

[java]Vector3f z = lookAtPos - cam.getPosition ();
Vector3f left = up.cross (z);
Vector3f newUp = z.cross (left);[/java]

of course it normalizes them after creating each one, then uses Quaternion.FromAxes ();

@safetyguard said: Thank you for answer. I just found the mistake. The solution for the computation between two points I found here: http://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another In my implementation I have forgotten to normalize the computed quaternion as pointed out in the stackoverflow post.

Recommend that you just use quaternion lookAt instead. Make sure the arrow is along the z-axis and you probably also make your life a lot easier if the base of the arrow is at one of the points.

@safetyguard said: Concerning the cam.lookUp method: is [java]cam.lookAt(new Vector3f(0, 0, 0), cam.getUp());[/java] correct? And why is [java]cam.lookAt(new Vector3f(0, 0, 0), Vector3f.ZERO);[/java] not right?

Wezrule answers this in more detail but the short answer: Because Vector3f.ZERO is not a direction vector.